设计模式二:七种结构型模式(上)

本文详细介绍了三种设计模式:适配器模式用于兼容不兼容接口,如电源适配器;桥接模式用于分离抽象与实现,允许两者独立变化,如颜色和形状的组合;组合模式处理部分与整体的关系,如公司组织结构。通过实例展示了这些模式在实际开发中的应用和优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结构性模式就像搭积木,将不同的类结合在一起形成契合的结构,包括以下几种:

  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰模式
  • 外观模式
  • 享元模式
  • 代理模式

适配器模式

适配器模式适用于有相关性但不兼容的结构,源接口通过一个中间件转换后才可以适用于目标接口,这个转换过程就是适配,这个中间件就是适配器。

class HomeBattery {
    int supply() {
        // 家用电源提供一个 220V 的输出电压
        return 220;
    }
}
class USBLine {
    void charge(int volt) {
        // 如果电压不是 5V,抛出异常
        if (volt != 5) throw new IllegalArgumentException("只能接收 5V 电压");
        // 如果电压是 5V,正常充电
        System.out.println("正常充电");
    }
}

class Adapter {
    int convert(int homeVolt) {
        // 适配过程:使用电阻、电容等器件将其降低为输出 5V
        int chargeVolt = homeVolt - 215;
        return chargeVolt;
    }
}

public class User {
    @Test
    public void chargeForPhone() {
        HomeBattery homeBattery = new HomeBattery();
        int homeVolt = homeBattery.supply();
        System.out.println("家庭电源提供的电压是 " + homeVolt + "V");

        Adapter adapter = new Adapter();
        int chargeVolt = adapter.convert(homeVolt);
        System.out.println("使用适配器将家庭电压转换成了 " + chargeVolt + "V");

        USBLine usbLine = new USBLine();
        usbLine.charge(chargeVolt);
    }
}

日常开发中经常会使用到各种各样的Adapter,都属于适配器模式的应用。但适配器模式并不推荐多用,只有遇到源接口无法改变时

,才应该考虑使用适配器模式,因为未雨绸缪要好多亡羊补牢,如果事前能预防接口的不同问题,就不会发生匹配问题。

桥接模式

将抽象部分与它的实现部分分离,使他们都可以独立的变化。它是一种对象结构型模式,又称为柄体模式或者接口模式。通俗的讲,如果一个对象有两种或者多种分类方式,并且两种分类方式都容易变化,这时如果使用继承很容易造成子类越来越多,所以更好的做法是这这种分类方式分离出来,让他们独立变化,使用时将不同的分类组合。

public interface IColor {
    String getColor();
}

public class Red implements IColor {
    @Override
    public String getColor() {
        return "红";
    }
}
public class Blue implements IColor {
    @Override
    public String getColor() {
        return "蓝";
    }
}

public class Yellow implements IColor {
    @Override
    public String getColor() {
        return "黄";
    }
}

public class Green implements IColor {
    @Override
    public String getColor() {
        return "绿";
    }
}

class Rectangle implements IShape {

    private IColor color;

    void setColor(IColor color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("绘制" + color.getColor() + "矩形");
    }
}
class Round implements IShape {

    private IColor color;

    void setColor(IColor color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("绘制" + color.getColor() + "圆形");
    }
}
class Triangle implements IShape {

    private IColor color;

    void setColor(IColor color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("绘制" + color.getColor() + "三角形");
    }
}

@Test
public void drawTest() {
    Rectangle rectangle = new Rectangle();
    rectangle.setColor(new Red());
    rectangle.draw();
    
    Round round = new Round();
    round.setColor(new Blue());
    round.draw();
    
    Triangle triangle = new Triangle();
    triangle.setColor(new Yellow());
    triangle.draw();
}

再来回顾一下官方定义:将抽象部分与他的实现部分分离,使他们都可以独立的变化。抽象部分指的是父类,对应本例中的形状类,实现部分指的是不同子类的区别之处。将子类的区别方式,也就是本例中的颜色分离成接口,通过组合的方式桥接颜色和形状,这就是桥接模式,它主要用于两个或多个同等级的接口。

组合模式

桥接模式用于同级的接口互相组合,而组合模式和桥接模式完全不一样,组合模式用于整体和部分的结构,当整体和部分有相似的结构,在操作时可以被一致对待时,就可以使用组合模式。例如:文件夹和子文件夹的关系,总公司与子公司的关系,树枝和分树枝的关系。在这些关系中,虽然整体包含了部分,但无论整体或部分,都具有一致的行为。

组合模式:又叫部分整体模式,是用于把一组相似的对象当做一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

考虑这样一个实际应用场景:设计一个公司的人员分布结构,如下图:

如果不使用组合模式:


public class Manager {
    // 职位
    private String position;
    // 工作内容
    private String job;
    // 管理的管理者
    private List<Manager> managers = new ArrayList<>();
    // 管理的职员
    private List<Employee> employees = new ArrayList<>();

    public Manager(String position, String job) {
        this.position = position;
        this.job = job;
    }
    
    public void addManager(Manager manager) {
        managers.add(manager);
    }

    public void removeManager(Manager manager) {
        managers.remove(manager);
    }

    public void addEmployee(Employee employee) {
        employees.add(employee);
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
    }

    // 做自己的本职工作
    public void work() {
        System.out.println("我是" + position + ",我正在" + job);
    }
    
    // 检查下属
    public void check() {
        work();
        for (Employee employee : employees) {
            employee.work();
        }
        for (Manager manager : managers) {
            manager.check();
        }
    }
}

public class Employee {
    // 职位
    private String position;
    // 工作内容
    private String job;

    public Employee(String position, String job) {
        this.position = position;
        this.job = job;
    }

    // 做自己的本职工作
    public void work() {
        System.out.println("我是" + position + ",我正在" + job);
    }
}

    @Test
    public void test() {
        Manager boss = new Manager("老板", "唱怒放的生命");
        Employee HR = new Employee("人力资源", "聊微信");
        Manager PM = new Manager("产品经理", "不知道干啥");
        Manager CFO = new Manager("财务主管", "看剧");
        Manager CTO = new Manager("技术主管", "划水");
        Employee UI = new Employee("设计师", "画画");
        Employee operator = new Employee("运营人员", "兼职客服");
        Employee webProgrammer = new Employee("程序员", "学习设计模式");
        Employee backgroundProgrammer = new Employee("后台程序员", "CRUD");
        Employee accountant = new Employee("会计", "背九九乘法表");
        Employee clerk = new Employee("文员", "给老板递麦克风");
        boss.addEmployee(HR);
        boss.addManager(PM);
        boss.addManager(CFO);
        PM.addEmployee(UI);
        PM.addManager(CTO);
        PM.addEmployee(operator);
        CTO.addEmployee(webProgrammer);
        CTO.addEmployee(backgroundProgrammer);
        CFO.addEmployee(accountant);
        CFO.addEmployee(clerk);

        boss.check();
    }

这样设计的弊端有:1.position、job字段重合,如果属性增加,比如工号、年龄等,将会有更多重复;2.管理者对其管理的管理者和职员需要区别对待,而Manager和Employee是有相似性的。

使用组合模式的设计方案:

public abstract class Component {
    // 职位
    private String position;
    // 工作内容
    private String job;

    public Component(String position, String job) {
        this.position = position;
        this.job = job;
    }

    // 做自己的本职工作
    public void work() {
        System.out.println("我是" + position + ",我正在" + job);
    }

    abstract void addComponent(Component component);

    abstract void removeComponent(Component component);

    abstract void check();
}

public class Manager extends Component {
    // 管理的组件
    private List<Component> components = new ArrayList<>();

    public Manager(String position, String job) {
        super(position, job);
    }

    @Override
    public void addComponent(Component component) {
        components.add(component);
    }

    @Override
    void removeComponent(Component component) {
        components.remove(component);
    }

    // 检查下属
    @Override
    public void check() {
        work();
        for (Component component : components) {
            component.check();
        }
    }
}

public class Employee extends Component {

    public Employee(String position, String job) {
        super(position, job);
    }

    @Override
    void addComponent(Component component) {
        System.out.println("职员没有管理权限");
    }

    @Override
    void removeComponent(Component component) {
        System.out.println("职员没有管理权限");
    }

    @Override
    void check() {
        work();
    }
}
@Test
    public void test(){
        Component boss = new Manager("老板", "唱怒放的生命");
        Component HR = new Employee("人力资源", "聊微信");
        Component PM = new Manager("产品经理", "不知道干啥");
        Component CFO = new Manager("财务主管", "看剧");
        Component CTO = new Manager("技术主管", "划水");
        Component UI = new Employee("设计师", "画画");
        Component operator = new Employee("运营人员", "兼职客服");
        Component webProgrammer = new Employee("程序员", "学习设计模式");
        Component backgroundProgrammer = new Employee("后台程序员", "CRUD");
        Component accountant = new Employee("会计", "背九九乘法表");
        Component clerk = new Employee("文员", "给老板递麦克风");
        boss.addComponent(HR);
        boss.addComponent(PM);
        boss.addComponent(CFO);
        PM.addComponent(UI);
        PM.addComponent(CTO);
        PM.addComponent(operator);
        CTO.addComponent(webProgrammer);
        CTO.addComponent(backgroundProgrammer);
        CFO.addComponent(accountant);
        CFO.addComponent(clerk);

        boss.check();
    }

可以看到,使用组合模式后,我们解决了之前的两个弊端。一是将共有的字段与方法移到了父类中,消除了重复,并且在客户端中,可以一致对待 Manager 和 Employee 类:

  • Manager 类和 Employee 类统一声明为 Component 对象

  • 统一调用 Component 对象的 addComponent 方法添加子对象即可。

组合模式中的安全方式与透明方式

         Employee 类虽然继承了父类的 addComponent 和 removeComponent 方法,但是仅仅提供了一个空实现,因为 Employee 类是不支持添加和移除组件的,这样违背了接口隔离原则。这种方式在组合模式中被称作透明方式。

这种方式有它的优点:让 Manager 类和 Employee 类具备完全一致的行为接口,调用者可以一致对待它们。

          但它的缺点也显而易见:Employee 类并不支持管理子对象,不仅违背了接口隔离原则,而且客户端可以用 Employee 类调用 addComponent 和 removeComponent 方法,导致程序出错,所以这种方式是不安全的。

我们可以将 addComponent 和 removeComponent 方法移到 Manager 子类中去单独实现,让 Employee 不再实现这两个方法,这种方式在组合模式中称之为安全方式。

安全方式遵循了接口隔离原则,但由于不够透明,Manager 和 Employee 类不具有相同的接口,在客户端中,我们无法将 Manager 和 Employee 统一声明为 Component 类了,必须要区别对待,带来了使用上的不方便。

安全方式和透明方式各有好处,在使用组合模式时,需要根据实际情况决定。但大多数使用组合模式的场景都是采用的透明方式,虽然它有点不安全,但是客户端无需做任何判断来区分是叶子结点还是枝节点,用起来是真香。

总结

适配器模式:适用于有共性但不兼容的接口(电源适配器例子);

桥接模式:用于同等级的接口互相组合(同一对象不同分类比如形状、颜色的组合例子);

组合模式:用于部分与整体的结构(公司普通员工和管理者的例子);

参考链接:设计模式

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值