Java复用三剑客:组合、继承、委托的”修仙传功术”
各位道友们好!我是会编程的吕洞宾,今天咱们来聊聊Java中的三种复用技术——组合、继承和委托。这就像修仙界的三种传功方式:组合是请外援,继承是传血脉,委托是雇帮手!
组合(Composition):”请外援道友”
什么是组合?
组合就像修仙者请其他道友来帮忙,把别人的能力”组合”成自己的实力:
// 外援道友:擅长炼丹
class Alchemist {
public void makePill(String material) {
System.out.println("炼制丹药: " + material);
}
}
// 外援道友:擅长布阵
class FormationMaster {
public void setupFormation(String name) {
System.out.println("布置阵法: " + name);
}
}
// 主修仙者:组合外援能力
public class MainCultivator {
// 组合:拥有其他对象
private Alchemist alchemist = new Alchemist();
private FormationMaster formationMaster = new FormationMaster();
public void cultivate() {
System.out.println("开始修炼...");
alchemist.makePill("千年灵芝"); // 使用外援的能力
formationMaster.setupFormation("聚灵阵"); // 使用外援的能力
System.out.println("修炼完成!");
}
public static void main(String[] args) {
MainCultivator mc = new MainCultivator();
mc.cultivate();
}
}
组合的特征(has-a关系)
组合体现的是”拥有”关系,就像”汽车拥有引擎”:
// 引擎类
class Engine {
private String type;
public Engine(String type) {
this.type = type;
}
public void start() {
System.out.println(type + "引擎启动!");
}
public void stop() {
System.out.println(type + "引擎停止!");
}
}
// 汽车类:拥有引擎
public class Car {
// 组合关系:汽车拥有引擎
private Engine engine;
public Car(String engineType) {
this.engine = new Engine(engineType); // 创建时组合
}
public void drive() {
System.out.println("汽车准备行驶...");
engine.start();
System.out.println("汽车行驶中...");
}
public void park() {
System.out.println("汽车停车...");
engine.stop();
}
}
优点:
- 灵活性高:可以随时更换组合的部件
- 封装性好:内部实现对外隐藏
- 运行时可变:可以在运行时动态改变行为
- 解耦设计:各个部件相对独立
缺点:
- 代码较多:需要手动转发方法调用
- 接口不统一:组合对象可能有不同的接口
- 对象较多:需要管理多个对象实例
组合的UML表明
组合关系用实心菱形箭头表明,表明整体拥有部分的生命周期:
┌─────────────┐
│ Car │
├─────────────┤
│ │
└──────┬──────┘
◄─┼─
│
┌──────┴──────┐
│ Engine │
└─────────────┘
反例:不当使用组合
// 反例:组合过度,导致过度复杂
public class OverComposed {
private A a = new A();
private B b = new B();
private C c = new C();
private D d = new D();
private E e = new E();
// 需要手动转发所有方法...
public void method1() { a.doA(); }
public void method2() { b.doB(); }
public void method3() { c.doC(); }
public void method4() { d.doD(); }
public void method5() { e.doE(); }
}
// 问题:过多的转发代码,维护困难
继承(Inheritance):”传血脉功法”
什么是继承?
继承就像修仙世家传功法,子代自动获得父辈的能力:
// 祖传功法
public class AncestralTechnique {
protected String familySecret = "家传秘法";
public void basicPractice() {
System.out.println("基础修炼:" + familySecret);
}
public void meditate() {
System.out.println("祖传打坐心法");
}
}
// 后代:继承祖传功法
public class Descendant extends AncestralTechnique {
private String personalInnovation;
public Descendant(String innovation) {
this.personalInnovation = innovation;
}
// 覆盖父类方法:改善祖传功法
@Override
public void meditate() {
super.meditate(); // 先调用父类方法
System.out.println("改善打坐:" + personalInnovation);
}
// 添加新方法:个人创新
public void specialMove() {
System.out.println("自创绝技:" + personalInnovation);
}
public static void main(String[] args) {
Descendant d = new Descendant("气贯长虹");
d.basicPractice(); // 继承来的方法
d.meditate(); // 覆盖后的方法
d.specialMove(); // 新增的方法
}
}
继承的特征(is-a关系)
继承体现的是”是一个”关系,就像”圆是一个形状”:
// 形状基类
public class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
public void draw() {
System.out.println("绘制一个" + color + "的形状");
}
public void erase() {
System.out.println("擦除形状");
}
}
// 圆形:是一个形状
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color); // 必须调用父类构造器
this.radius = radius;
}
// 覆盖父类方法
@Override
public void draw() {
System.out.println("绘制一个" + color + "的圆形,半径:" + radius);
}
// 添加新方法
public double getArea() {
return Math.PI * radius * radius;
}
}
// 使用向上转型
public class ShapeDemo {
public static void main(String[] args) {
// 向上转型:圆是一个形状
Shape shape = new Circle("红色", 5.0);
shape.draw(); // 多态:调用Circle的draw方法
// 只能调用Shape中定义的方法
// shape.getArea(); // 错误!Shape接口中没有此方法
}
}
优点:
- 代码复用:自动获得父类功能
- 多态支持:支持运行时绑定
- 接口统一:所有子类有一样的基本接口
- 编译时检查:类型安全
缺点:
- 紧耦合:父类修改可能影响所有子类
- 破坏封装:子类可能依赖父类实现细节
- 不够灵活:继承关系在编译时确定
- 单一继承:Java只支持单继承
继承的UML表明
继承关系用空心三角形箭头表明:
┌─────────────┐
│ Shape │
├─────────────┤
│ +draw() │
│ +erase() │
└──────┬──────┘
△
│
┌──────┴──────┐
│ Circle │
├─────────────┤
│ +getArea() │
└─────────────┘
反例:不当使用继承
// 反例:不合理的继承关系
public class Stack extends ArrayList {
public void push(Object o) {
add(o);
}
public Object pop() {
return remove(size() - 1);
}
}
// 问题:栈不是列表,暴露了不需要的方法(如add(int, Object))
// 正确做法:使用组合,把ArrayList作为内部实现
委托(Delegation):”雇专业帮手”
什么是委托?
委托就像雇一个专业帮手,把工作交给专家处理:
// 专业帮手:擅长数据处理
class DataProcessor {
public String process(String data) {
return "处理后的数据:" + data.toUpperCase();
}
public String analyze(String data) {
return "分析结果:" + data.length() + "个字符";
}
public String format(String data) {
return "格式化:" + data.toLowerCase();
}
}
// 主类:委托给专业帮手
public class BusinessLogic {
// 委托:持有帮手引用
private DataProcessor processor = new DataProcessor();
// 可以选择性地暴露帮手的方法
public String doBusiness(String input) {
// 委托给processor处理
String processed = processor.process(input);
String analyzed = processor.analyze(processed);
return analyzed;
}
// 也可以直接暴露某些方法
public String delegateFormat(String data) {
return processor.format(data);
}
// 不暴露analyze方法,保持控制
}
委托的特征
委托介于组合和继承之间,既使用组合,又像继承一样暴露接口:
// 宇宙飞船控制器
public class SpaceShipControls {
public void up(int velocity) {
System.out.println("向上移动:" + velocity);
}
public void down(int velocity) {
System.out.println("向下移动:" + velocity);
}
public void left(int velocity) {
System.out.println("向左移动:" + velocity);
}
public void right(int velocity) {
System.out.println("向右移动:" + velocity);
}
public void forward(int velocity) {
System.out.println("向前移动:" + velocity);
}
public void back(int velocity) {
System.out.println("向后移动:" + velocity);
}
public void turboBoost() {
System.out.println("涡轮加速!");
}
}
// 反例:使用继承(不合适)
public class DerivedSpaceShip extends SpaceShipControls {
private String name;
public DerivedSpaceShip(String name) {
this.name = name;
}
// 问题:飞船不是控制器,暴露了不需要的方法
}
// 正例:使用委托
public class SpaceShipDelegation {
private String name;
// 组合:拥有控制器
private SpaceShipControls controls = new SpaceShipControls();
public SpaceShipDelegation(String name) {
this.name = name;
}
// 委托方法:只暴露需要的操作
public void flyForward(int speed) {
System.out.println(name + "起飞...");
controls.forward(speed);
}
public void turnLeft(int speed) {
controls.left(speed);
}
public void turnRight(int speed) {
controls.right(speed);
}
public void emergencyBoost() {
controls.turboBoost();
}
// 不暴露up()、down()、back()方法
// 保持对接口的控制
}
优点:
- 完全控制:可以只暴露部分接口
- 灵活性高:可以运行时更换委托对象
- 解耦设计:委托对象可以独立变化
- 避免继承缺点:没有继承的紧耦合问题
缺点:
- 代码较多:需要手动编写委托方法
- 无语言支持:Java不直接支持委托语法
- 维护成本:需要维护委托关系
委托与继承的区别
// 场景:温度控制器系统
public class Thermostat {
// 方式一:继承(不灵活)
class OldCoolingSystem {
public void cool() {
System.out.println("制冷");
}
}
// 新需求:需要既能制冷又能制热
class NewHeatPump extends OldCoolingSystem {
// 问题:只能访问cool(),无法添加heat()
public void heat() {
System.out.println("制热");
}
}
// 使用NewHeatPump时,只能调用cool(),heat()用不了
// 方式二:更好的设计
abstract class TemperatureControl {
public abstract void adjustTemperature();
}
class CoolingSystem extends TemperatureControl {
@Override
public void adjustTemperature() {
cool();
}
private void cool() {
System.out.println("制冷");
}
}
class HeatPump extends TemperatureControl {
@Override
public void adjustTemperature() {
// 可以根据需要选择制冷或制热
if (needCooling) cool();
else heat();
}
private void cool() {
System.out.println("制冷");
}
private void heat() {
System.out.println("制热");
}
}
// 使用委托:控制器委托给温度系统
class SmartController {
private TemperatureControl control;
public void setControl(TemperatureControl control) {
this.control = control; // 运行时切换
}
public void adjust() {
control.adjustTemperature();
}
}
}
三种技术的对比与选择
对比表格
|
特性 |
组合 (Composition) |
继承 (Inheritance) |
委托 (Delegation) |
|
关系 |
has-a (拥有) |
is-a (是一个) |
uses-a (使用) |
|
灵活性 |
高,运行时可变 |
低,编译时确定 |
高,运行时可变 |
|
耦合度 |
低 |
高 |
中 |
|
代码量 |
中 |
少 |
多 |
|
复用方式 |
复用实现 |
复用接口和实现 |
复用实现 |
|
多态支持 |
无 |
有 |
无 |
|
UML表明 |
实心菱形 |
空心三角形 |
依赖箭头 |
选择指南
优先使用组合的情况
// 场景:汽车系统
public class Car {
// 优先思考组合:汽车拥有引擎、车轮、门
private Engine engine;
private Wheel[] wheels;
private Door[] doors;
public Car() {
engine = new Engine();
wheels = new Wheel[4];
for (int i = 0; i < 4; i++) {
wheels[i] = new Wheel();
}
doors = new Door[2];
doors[0] = new Door();
doors[1] = new Door();
}
}
// 缘由:汽车不是引擎,汽车有引擎
适合使用继承的情况
// 场景:图形系统
abstract class Shape {
public abstract void draw();
public abstract double getArea();
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("绘制圆形");
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
// 缘由:圆是一个形状,需要多态支持
使用委托的情况
// 场景:窗口系统
interface Window {
void draw();
void resize(int width, int height);
String getDescription();
}
class SimpleWindow implements Window {
@Override
public void draw() {
System.out.println("绘制简单窗口");
}
@Override
public void resize(int width, int height) {
System.out.println("调整窗口大小:" + width + "x" + height);
}
@Override
public String getDescription() {
return "简单窗口";
}
}
// 装饰器:使用委托添加功能
class ScrollableWindow implements Window {
private Window window; // 委托给基础窗口
public ScrollableWindow(Window window) {
this.window = window;
}
@Override
public void draw() {
window.draw();
drawScrollBars();
}
@Override
public void resize(int width, int height) {
window.resize(width, height);
}
@Override
public String getDescription() {
return window.getDescription() + " + 滚动条";
}
private void drawScrollBars() {
System.out.println("绘制滚动条");
}
}
// 缘由:需要动态添加功能,保持接口一致
关键问题:需要向上转型吗?
文档中给出了黄金法则:
// 判断标准:是否需要向上转型?
public class ReuseDecisionMaker {
public void decideReuseMethod(Class<?> baseClass, Class<?> derivedClass) {
boolean needUpcasting = checkIfNeedUpcasting(derivedClass, baseClass);
if (needUpcasting) {
// 需要向上转型 → 使用继承
System.out.println("使用继承:需要多态支持");
} else {
// 不需要向上转型 → 优先思考组合
System.out.println("优先使用组合:更灵活");
// 如果需要控制接口暴露 → 思考委托
boolean needInterfaceControl = checkIfNeedInterfaceControl();
if (needInterfaceControl) {
System.out.println("或使用委托:控制接口暴露");
}
}
}
private boolean checkIfNeedUpcasting(Class<?> derived, Class<?> base) {
// 判断标准:派生类是否真的"是一个"基类?
// 能否说:圆是一个形状?汽车是一个引擎?
return derived.getName().equals("Circle") && base.getName().equals("Shape");
}
}
实战案例:设计一个”修仙门派管理系统”
让我们综合运用三种复用技术:
// 基类:修仙者
abstract class Cultivator {
protected String name;
protected int cultivationLevel;
public Cultivator(String name) {
this.name = name;
this.cultivationLevel = 1;
}
// 模板方法模式:使用继承
public final void dailyRoutine() {
morningPractice();
noonMeditation();
eveningCultivation();
nightRest();
}
protected abstract void morningPractice();
protected abstract void noonMeditation();
protected abstract void eveningCultivation();
protected void nightRest() {
System.out.println(name + "夜间休憩");
}
}
// 具体修仙者:剑修
class SwordCultivator extends Cultivator {
// 组合:拥有宝剑
private Sword sword;
// 委托:拥有助手
private AlchemyAssistant assistant;
public SwordCultivator(String name, Sword sword) {
super(name);
this.sword = sword;
this.assistant = new AlchemyAssistant();
}
@Override
protected void morningPractice() {
System.out.println(name + "晨练剑法");
sword.practice(); // 使用组合对象
}
@Override
protected void noonMeditation() {
System.out.println(name + "午时打坐");
assistant.preparePill("凝神丹"); // 委托给助手
}
@Override
protected void eveningCultivation() {
System.out.println(name + "晚间修炼");
sword.enhance(); // 使用组合对象
}
// 新增功能:不需要向上转型
public void duel(SwordCultivator opponent) {
System.out.println(name + "与" + opponent.name + "比剑");
}
}
// 组合对象:宝剑
class Sword {
private String name;
private int sharpness;
public Sword(String name) {
this.name = name;
this.sharpness = 100;
}
public void practice() {
System.out.println("练习" + name + "剑法");
}
public void enhance() {
sharpness += 10;
System.out.println(name + "锋利度提升至:" + sharpness);
}
}
// 委托对象:炼丹助手
class AlchemyAssistant {
public void preparePill(String pillName) {
System.out.println("助手准备丹药:" + pillName);
}
public void collectHerbs() {
System.out.println("助手采集草药");
}
}
// 使用多态:需要向上转型
class SectManager {
public void organizePractice(Cultivator[] cultivators) {
for (Cultivator c : cultivators) {
c.dailyRoutine(); // 多态调用
}
}
public static void main(String[] args) {
Sword sword1 = new Sword("青霜剑");
Sword sword2 = new Sword("赤霄剑");
SwordCultivator cultivator1 = new SwordCultivator("张三", sword1);
SwordCultivator cultivator2 = new SwordCultivator("李四", sword2);
// 向上转型:需要多态
Cultivator[] allCultivators = {cultivator1, cultivator2};
SectManager manager = new SectManager();
manager.organizePractice(allCultivators);
// 不需要向上转型:直接使用具体功能
cultivator1.duel(cultivator2);
}
}
最佳实践总结
复用技术口诀
组合像请外援,灵活又安全 继承像传血脉,统一能多态 委托像雇帮手,可控接口藏 选择看关系,is-a才继承1 需要向上转,继承是首选1 否则用组合,委托控接口
实用提议
- 优先组合原则:文档明确指出”在创建新类时第一要思考组合”
- 继承要谨慎:只有当需要向上转型和多态时才使用继承
- 委托控接口:当需要控制暴露哪些方法时思考委托
- 判断标准:问自己”是否需要向上转型?”
- 避免继承滥用:不要由于方便而使用继承,要思考是否真的是is-a关系
记住文档中的忠告:“尽量少使用继承,除非的确 使用继承是有协助的”。好的设计应该像修仙界的生态系统——既有稳定的传承(继承),又有灵活的合作(组合),还有专业的服务(委托),三者结合才能构建出强劲而健壮的系统!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...
