Java复用三剑客:组合、继承、委托的”修仙传功术”

内容分享2周前发布
0 0 0

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();
    }
}

优点

  1. 灵活性高:可以随时更换组合的部件
  2. 封装性好:内部实现对外隐藏
  3. 运行时可变:可以在运行时动态改变行为
  4. 解耦设计:各个部件相对独立

缺点

  1. 代码较多:需要手动转发方法调用
  2. 接口不统一:组合对象可能有不同的接口
  3. 对象较多:需要管理多个对象实例

组合的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接口中没有此方法
    }
}

优点

  1. 代码复用:自动获得父类功能
  2. 多态支持:支持运行时绑定
  3. 接口统一:所有子类有一样的基本接口
  4. 编译时检查:类型安全

缺点

  1. 紧耦合:父类修改可能影响所有子类
  2. 破坏封装:子类可能依赖父类实现细节
  3. 不够灵活:继承关系在编译时确定
  4. 单一继承: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()方法
    // 保持对接口的控制
}

优点

  1. 完全控制:可以只暴露部分接口
  2. 灵活性高:可以运行时更换委托对象
  3. 解耦设计:委托对象可以独立变化
  4. 避免继承缺点:没有继承的紧耦合问题

缺点

  1. 代码较多:需要手动编写委托方法
  2. 无语言支持:Java不直接支持委托语法
  3. 维护成本:需要维护委托关系

委托与继承的区别

// 场景:温度控制器系统
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 否则用组合,委托控接口

实用提议

  1. 优先组合原则:文档明确指出”在创建新类时第一要思考组合”
  2. 继承要谨慎:只有当需要向上转型和多态时才使用继承
  3. 委托控接口:当需要控制暴露哪些方法时思考委托
  4. 判断标准:问自己”是否需要向上转型?”
  5. 避免继承滥用:不要由于方便而使用继承,要思考是否真的是is-a关系

记住文档中的忠告:“尽量少使用继承,除非的确 使用继承是有协助的”。好的设计应该像修仙界的生态系统——既有稳定的传承(继承),又有灵活的合作(组合),还有专业的服务(委托),三者结合才能构建出强劲而健壮的系统!

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...