【了解一下常见的设计模式】

文章目录

  • 了解一下常用的设计模式(工厂、包装、关系)
    • 导语
    • 设计模式辨析系列
  • 工厂篇
    • 工厂
    • 什么是工厂
    • 简单工厂「模式」(Simple Factory「Pattern」)
      • 简单工厂代码示例:简单计算器
      • 优点:
      • 缺点:
    • 静态工厂模式
      • 特点:
    • 工厂方法模式(Factory Method Pattern)
      • 什么是工厂方法模式?
      • 工厂方法模式代码示例:简单计算器
      • 优点
      • 与简单工厂的异同:
    • 抽象工厂模式(Abstract Factory Pattern)
      • 什么是抽象工厂模式?
      • 抽象工厂模式代码示例:计算器生产
      • 优点
      • 缺点
      • 与工厂方法模式的异同:
    • 拓展
    • 总结
  • 包装篇
    • 导语
    • 装饰模式(Decorator Pattern)
      • 什么是装饰模式?
      • 装饰者&被装饰对象
      • 装饰模式代码实例:
      • 装饰者模式和继承的区别:
      • 装饰模式的优缺点
    • 什么是适配器模式?
      • 适用场景
      • 适配器模式代码实例:
      • 适配器模式优缺点
      • 适配器模式的类型
        • 类适配器:继承被适配者类和目标类
        • 对象适配器:利用组合的方式将请求传送给被适配者
      • 与装饰者模式的区别:
    • 外观模式(Facade Pattern)
      • 什么是外观模式?
      • 适用场景
      • 外观模式代码实例:
      • 外观模式优缺点:
      • 与装饰模式、适配器模式的区别:
    • 代理模式(Proxy Pattern)
      • 什么是代理模式
      • 代理控制访问方式:
      • 代理模式代码实例:
      • 代理模式的优缺点:
      • 与装饰模式、适配器模式的区别
      • 代理模式变体类型(简单了解)
    • 总结
    • 四种设计模式的目的
  • 关系篇
    • 导语
    • 继承
      • 继承关系的潜在问题
    • 桥接模式(Bridge Pattern)
      • 什么是桥接模式?
      • 桥接模式如何应对变化?
      • 合成/聚合复用原则(CARP)
      • 桥接模式示意图
      • 桥接模式代码实例:
      • 桥接模式的优缺点
    • 组合模式(Composite Pattern)
      • 什么是组合模式?
      • 组合、组件、叶节点、枝节点
      • 组合模式的分类
        • 透明方式
        • 安全方式
      • 组合模式代码实例:
      • 何时使用组合模式
      • 组合模式的优缺点
    • 享元模式(Flyweight Pattern)
      • 什么是享元模式?
      • 享元对象的状态分类
        • 内部状态
        • 外部状态
      • 享元模式的主要角色:
        • 享元工厂(Flyweight Factory)
        • 抽象享元类(Flyweight)
        • 具体享元类(Concrete Flyweight)
        • 非享元类(Unshared Concrete Flyweight )
      • 享元模式代码实例:
      • 什么时候使用享元模式
      • 享元模式的优缺点
      • 享元模式的应用
    • 中介者模式(Mediator Pattern)
      • 什么是中介者模式?
        • 中介者
        • 同事类
      • 中介者模式的关系图
      • 中介者模式与桥接模式:
      • 中介者模式代码实例:
      • 中介者模式的优缺点
      • 中介者模式的应用:
    • 总结

img

了解一下常用的设计模式(工厂、包装、关系)

导语

**设计模式(Design Pattern)**是软件开发过程中一般问题的解决方案,是无数面向对象软件开发人员的经验总结,对于软件设计开发十分重要。然而由于设计模式种类繁多,内容偏理论,缺乏开发经验对于相关概念的理解也比较困难,同时其中不乏很多类型相似的设计模式,更是让对设计模式的学习难上加难。

本篇为了巩固对于设计模式的理解,了解不同设计模式的特点

资料来源如下:

设计模式辨析系列

设计模式辨析——工厂篇(简单工厂、静态工厂、工厂方法、抽象工厂)

对于使用到工厂的设计模式,在理解使用场景和概念后,只要记住以下几句话就可以确保掌握了。

  1. 所以工厂都是用来封装对象的创建
  2. 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合
  3. 工厂帮助我们针对抽象编程,而不是针对具体类编程

设计模式辨析——包装篇(装饰模式、适配器模式、外观模式、代理模式)

记住关于包装篇的四种设计模式的目的:

  • 装饰模式:将一个对象包装起来以增加新的行为和责任
  • 适配器模式:将一个对象包装起来以改变其接口
  • 外观模式:将一群对象包装起来以简化其接口
  • 代理模式:将一个对象包装起来以控制对它的访问

设计模式辨析——关系篇(桥接模式、组合模式、享元模式、中介者模式)

桥接模式、组合模式和中介者模式都改变类或对象之间的关系,从而达到各自减少对象数量或降低代码复杂度的目的。

  • 桥接模式使用合成/聚合复用原则,变继承为聚合,**将抽象部分与它的实现部分分离,**减少了子类数量,降低了代码的耦合度

  • 组合模式用树形结构表示**“部分-整体”**的层次结构来管理对象,使得用户可以用统一的接口使用组合结构中的所有对象,简化了代码,提高了可扩展性

  • 中介者模式使用中介者负责控制和协调一组对象间的交互,将多对多的关系转为一对多降低了对象之间的耦合度,提升了代码的复用性、可扩展性。

  • 享元模式则主要为了解决内存资源浪费问题,通过对对象之间相似部分的抽取,利用共享技术,减少对象实例。

区分这几种设计模式的关键,依旧在于把握目的(意图)的不同。

工厂篇

工厂

找出会变化的部分,把它们从不变的部分分离出来

当软件开发过程中使用接口时,代码中往往需要实例化大量的具体类,而这些实现大多是由一些条件决定的。对于如何实例化对象这个问题,往往考虑用一个单独的类来处理创造实例的过程,这个类就是工厂(factory)

什么是工厂

针对接口编程,不针对实现编程

定义:用于封装创建对象的代码,负责处理创建对象的细节的类被称为工厂(Factory)

优点: 将创建对象的代码集中在一个对象或方法中,避免代码中的重复,便于以后的维护

简单工厂「模式」(Simple Factory「Pattern」)

简单工厂「模式」是对于工厂最基础的应用,但它其实不能算作“工厂模式”,它不是一个设计模式,像是一种编程习惯。

简单工厂代码示例:简单计算器

通过代码实现一个简单计算器,具有加减乘除的运算功能

public class OperationFactory {
    public static Operation createOperation(char operator) {
	Operation operation = null;

	switch (operator) { //可以看到简单工厂将运算类的实例放在工厂类中实现
//通过分支选择具体的实现类
	case '+':
	    operation = new OperationAdd();
	    break;
	case '-':
	    operation = new OperationSub();
	    break;
	case '*':
	    operation = new OperationMul();
	    break;
	case '/':
	    operation = new OperationDiv();
	    break;
	default:
	    throw new RuntimeException("unsupported operation");
	}

	return operation;
    }
}
public class OperationAdd extends Operation {

    @Override
    public double result() { //具体的操作子类只需要实现具体运算
		return numberA + numberB;
    }

}
public class Calculator {
    public static void main(String[] args) {
        Operation operation;
        char operator;

        operator = '+';
    //使用过程中,在客户端提供具体实例的参数,传入工厂类实现
        operation = OperationFactory.createOperation(operator);
        operation.numberA = 1.2;
        operation.numberB = 2.3;
    //具体的运算过程,客户端不可见
        System.out.println(operation.result());
    }
}

开放—封闭原则:对扩展开放,对修改关闭

优点:

  1. 根据客户端的选择条件动态实例化相关的类,去除了客户端与具体类的依赖。
  2. 添加新的类只需要在工厂类中添加新的分支

缺点:

  1. 不符合“开放—封闭原则”,简单工厂中每一次扩展都需要对工厂类进行修改

静态工厂模式

将工厂类中的创建对象的功能定义为静态的,就是静态工厂模式,它同样不是一种设计模式。

特点:

  1. 不需要使用创建对象的方法实例化对象
  2. 不能通过继承来改变创建方法的行为

由于静态工厂与简单工厂的差别不大,在此就不详细展开。关于静态工厂模式更多的内容这篇文章中有详细描述「https://zhuanlan.zhihu.com/p/157099580」,感兴趣可以了解。

工厂方法模式(Factory Method Pattern)

什么是工厂方法模式?

工厂方法模式定义了一个创建对象的接口,让子类决定要实例化的类是哪一个,工厂方法把一个类的实例化推迟到其子类

工厂方法模式代码示例:简单计算器

通过代码实现一个简单计算器,具有加减乘除的运算功能

public interface IFactory { //所有工厂的抽象,工厂接口
    public Operation createOperation(); //创建实例类的方法,延迟到工厂子类中实现
}
//工厂的具体实现类
class AddFactory implements IFactory {
    @Override
    public Operation createOperation() { //实现对操作类的实现 
		return new OperationAdd();
    }
}

class SubFactory implements IFactory {
    @Override
    public Operation createOperation() {
		return new OperationSub();
    }
}

class MulFactory implements IFactory {
    @Override
    public Operation createOperation() {
		return new OperationMul();
    }
}

class DivFactory implements IFactory {
    @Override
    public Operation createOperation() {
		return new OperationDiv();
    }
}

public class FactoryClient {
    public static void main(String[] args) {
    //由客户端选择实例化的工厂类(除法)
        IFactory operFactory = new DivFactory();
        Operation operation = operFactory.createOperation(); //进行运算操作

        operation.numberA = 3.4;
        operation.numberB = 4.5;
        System.out.println(operation.result());
    }
}

优点

  1. 通过让工厂子类决定应该创建的对象是什么,来达到将对象创建的过程封装的目的。
  2. 针对接口编程,不针对实现编程,代码更具弹性,扩展性更好
  3. 符合开放-封闭原则:客户端决定实例化哪一个工厂来创建类,在扩展时不需要修改工厂类
  4. 解耦合:把创建对象的具体实现移到具体的工厂子类,在工厂接口类并不需要知道实际创建的对象是哪一个类,解耦了客户端中关于超类的代码和子类对象创建代码。

与简单工厂的异同:

同:

  1. 集中封装了对象的创建,降低客户端对特定实现的依赖以及与产品对象的耦合

异:

  1. 简单工厂把有关实例化的全部事情,都在工厂类中处理(没有抽象工厂类),然而工厂方法模式创建了一个框架,让工厂子类决定要如何实现。
  2. 克服了简单工厂违背“开放-封闭原则”的缺点,又保持了封装对象创建过程的优点,对简单工厂的进一步抽象和推广

抽象工厂模式(Abstract Factory Pattern)

依赖倒置原则:要依赖抽象,不要依赖具体的类

  • 避免在OO设计中违反依赖倒置原则的指导方针:

    • 变量不可以持有具体类的引用
    • 不要让类派生自具体类
    • 不要覆盖基类中已实现的方法

什么是抽象工厂模式?

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而不需要指明它们具体的类。

抽象工厂中的每一个方法创建一个具体类,实际利用工厂方法实现

抽象工厂模式代码示例:计算器生产

假设现在有流水线负责生产计算器按钮,并对计算器进行组装、检查,计算器按钮的颜色系列有黑色系列、白色系列等需要实现

//按钮工厂
public interface ButtonFactory{
//生产四种不同的运算按钮
    public AddButton createAddButton();
    public SubButton createSubButton();
    public DivButton createDivButton();
    public MulButton createMulButton();
}
/**
* 需要提前说明的是WhiteAddButton是AddButton的具体实现类,
* 不同的运算按钮并不是同一类,代码省略了Button相关类的声明
*/
//白色系列按钮工厂
public class WhiteButtonFactory implements ButtonFactory{
    @Override
    public AddButton createAddButton() {
    	return new WhiteAddButton();
    }
    @Override
    public SubButton createAddButton() {
    	return new WhiteSubButton();
    }
    @Override
    public DivButton createAddButton() {
    	return new WhiteDivButton();
    }
    @Override
    public MulButton createAddButton() {
    	return new WhiteMulButton();
    }
}

//黑色系列按钮工厂
public class BlackButtonFactory implements ButtonFactory{
    @Override
    public AddButton createAddButton() {
    	return new BlackAddButton();
    }
    @Override
    public SubButton createAddButton() {
    	return new BlackSubButton();
    }
    @Override
    public DivButton createAddButton() {
	    return new BlackDivButton();
    }
    @Override
    public MulButton createAddButton() {
    	return new BlackMulButton();
    }
}
//计算器类
public class Calculator() {
    AddButton addButton;
    DivButton divButton;
    SubButton subButton;
    MulButton mulButton;
    public Calculator(ButtonFactory buttonFactory) {
		this.buttonFactory = buttonFactory;
    }
    public void prepare() {
    	addButton = buttonFactory.createAddButton();
    	subButton = buttonFactory.createSubButton();
    	divButton = buttonFactory.createDivButton();
		mulButton = buttonFactory.createMulButton();
    }
    //组装、测试方法
    public void assembly();
    public void test();
}
public abstract class AssemblyLine {
    //生产方法
    public Calculator produce(){
    	Calculator calculator;
    	calculator = createCalculator();
    	calculator.prepare();
    	calculator.assembly();
    	calculator.test();
    	return calculator;
    }
    protected abstract Calculator createCalculator();
}
//白色按钮流水线
public class WhiteAssemblyLine extends AssemblyLine {
    @Override
    protected  Calculator createCalculator(){
    	Calculator calculator = null;
    	ButtonFactory buttonFactory = new WhiteButtonFactory();
    	calculator = new Calculator(buttonFactory);
        return calculator;
    }
}
//黑色按钮流水线
public class BlackAssemblyLine extends AssemblyLine {
    @Override
    protected  Calculator createCalculator(String type){
    	Calculator calculator = null;
    	ButtonFactory buttonFactory = new BlackButtonFactory();
    	calculator = new Calculator(buttonFactory);
    	return calculator;
    }
}
public class CalculatorClient() {
    public static void main(String[] args) {
    	//声明流水线
    	AssemblyLine whiteLine = new WhiteAssemblyLine();
    	AssemblyLine blackLine = new BlackAssemblyLine();
    	//决定生成的计算器实例,可以看到系列按钮的创建过程与客户端分离
    	Calculator calculator1 = whiteLine.produce();
    	Calculator calculator2 = blackcLine.produce();
    }
}

需要注意的是,由于抽象工厂类的使用场景针对「系列」,所以这里代码示例的场景发生了改变,涉及类比较多,请一定搞清楚。其中抽象工厂类的主要作用是把不同系列的按钮创建封装在了一起,因此在改变计算器的按钮系列时,我们只需要更换流水线类就好了(流水线类在这里其实只是工厂类上的一个封装,可以简单理解为更换了工厂类)。你可以设想一下,要是使用的是简单工厂、工厂方法模式,要想实现上述代码示例的功能,代码应该怎么写?改动按钮系列时,需要改动那些地方?

优点

  1. 易于交换产品系列,改变具体工厂类只需要在初始化时进行,十分方便就可以使用不同产品配置
  2. 允许客户使用抽象的接口创建具体的一组产品,创建过程与客户端分离,降低耦合度

缺点

  1. 增加需求时需要改动接口,对每一个具体工厂类进行改动,较为复杂

与工厂方法模式的异同:

同:

  1. 集中封装了对象的创建,降低客户端对特定实现的依赖以及与产品对象的耦合

异:

  1. 创建对象
  • 工厂方法模式:继承

    • 通过工厂子类创建对象,客户端只需要知道具体的抽象类型,由工厂子类决定具体类型,只负责将客户从具体类型中解耦
  • 抽象工厂模式:对象的组合

    • 提供用来创建一个产品家族的抽象类,这个类型的子类定义产品被创建的方法,使用时通过实例化具体工厂类,并将工厂类传入针对抽象类型写的代码中,把客户从所使用的实际具体产品中解耦
  1. 抽象工厂集合了一群相关的产品类,而工厂方法只需要创建一个产品。抽象工厂中的每一个方法创建一个具体类,实际是利用工厂方法实现,

拓展

  • 利用简单工厂改进抽象工厂:

    • 减少具体工厂类,达到解耦目的
  • 反射+抽象工厂:

    • 利用依赖注入消除switch的使用

总结

对于使用到工厂的设计模式,在理解使用场景和概念后,只要记住以下几句话就可以确保掌握了。

  1. 所以工厂都是用来封装对象的创建
  2. 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合
  3. 工厂帮助我们针对抽象编程,而不是针对具体类编程

包装篇

导语

当你在编写代码时,需要扩展一个类的功能,或者是当前类的接口不能满足需求时,你会选择怎么做。重新编写子类,通过继承加入功能?修改原有类的接口使其符合现有环境?但你会发现这些改动是不完美的,它们并不符合面向对象的「开放-关闭原则」。

开放-关闭原则: 对扩展开放,对修改关闭

在软件设计模式中有一个更好的答案——包装

下面介绍的四种设计模式都围绕着“包装”展开,那么首先先简单了解一下这些设计模式:

  • 装饰者模式(Decorator Pattern):包装另一个对象,并提供额外的行为
  • 适配器模式(Adapter Pattern):包装另一个对象,并提供不同的接口
  • 外观模式(Facade Pattern):包装许多对象以简化它们的接口
  • 代理模式(Proxy Pattern):包装另一个对象,并控制对它的访问

装饰模式(Decorator Pattern)

当你想要扩展一个类的功能时,最直接的方法就是编写一个子类,然后在子类中加入新的功能函数。但是这种更改往往会导致很多问题,例如:想要去掉父类中的一个方法时。这不是一种弹性设计,不符合「开放-关闭原则」。装饰模式提供了继承之外的一种新思路。

什么是装饰模式?

定义:动态地将责任附加到对象上,给对象添加额外的职责,对于扩展功能来说, 装饰者提供了比继承更有弹性的方法。

  • 装饰者可以在所委托的被装饰者的行为之前或之后,加上自己的行为,达到特定的目的
  • 使用装饰者模式的过程中,可以使用多个装饰类包装对象(数量没有限制),客户端可以在运行时有选择地使用装饰功能包装对象

装饰者&被装饰对象

装饰者和被装饰对象需要具有相同的父类,装饰者模式用继承达到类型匹配的目的

  • 每个装饰对象的实现与如何使用这个对象分离开,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链中。

使用方法:将需要实例化的对象传入装饰类中进行包装

http://Java.IO库就是以装饰者模式来编写的

装饰模式代码实例:

//Component定义了一个对象接口,通过装饰类可以给这些对象动态地添加职责
public abstract class Component {
    public abstract void operation();
}
/** Decorator,装饰抽象类,继承了Component
* 从外类来扩展Component类的功能,但对于Component来说,
* 是无需知道Decorator的存在的
*/
public abstract class Decorator extends Component {
	protected Component component;
//获取被装饰的对象
    public Component getComponent() {
	return component;
    }
//设置被装饰的对象
    public void setComponent(Component component) {
		this.component = component;
    }
    @Override
    public void operation() {
        if (component != null) {
        	component.operation();
        }
    }
}
//具体装饰类,可以为类加入新的行为
class ConcreteDecoratorA extends Decorator {
    private String addedState;

    @Override
    public void operation() {
        // 首先运行原Component的operation(),再执行本类的功能,如addedState,相当于对原Component进行了装饰
        super.operation();
        addedState = "A中的new state ";
        System.out.println(addedState + "具体装饰对象A的操作");
    }
}

class ConcreteDecoratorB extends Decorator {
    @Override
    public void operation() {
        super.operation();
        addedBehavior();
        System.out.println("具体装饰对象B的操作");
    }
    public void addedBehavior() {
		System.out.print("B中的新增行为 ");
    }
}

class ConcreteDecoratorC extends Decorator {
    @Override
    public void operation() {
        super.operation();
        System.out.println("C没有特殊行为 " + "具体装饰对象C的操作");
    }

}
//ConcreteComponent是定义一个具体的对象,也可以给这个对象添加一些职责
public class ConcreteComponent extends Component {
    @Override
    public void operation() {
		System.out.println("具体对象的操作");
    }

}
//装饰模式客户端调用代码
public class DecoratorClient {
    public static void main(String[] args) {
        ConcreteComponent concreteComponent = new ConcreteComponent();
        //声明装饰类A、B、C
        ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA();
        ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB();
        ConcreteDecoratorC concreteDecoratorC = new ConcreteDecoratorC();
        //装饰的过程就像是层层包装,不断地装饰类包装对象,达到添加功能的目的
        concreteDecoratorA.setComponent(concreteComponent); //装饰类A包装对象
        concreteDecoratorB.setComponent(concreteDecoratorA); //装饰类B包装装饰类A(对象已经被包装在装饰类A中)
        concreteDecoratorC.setComponent(concreteDecoratorB); //装饰类C包装装饰类B
        concreteDecoratorC.operation();
    }
}

在DecoratorClient中,经过装饰类的包装后,最终对象关系如下,被装饰者concreteComponent包装在装饰类中,同时具有了各个装饰类附加的方法和行为

各个对象的包装关系:

img

装饰者模式和继承的区别:

  1. 继承设计子类,是在编译时静态决定的,通过组合的做法扩展对象,可以在运行时动态地进行扩展
  2. 装饰者模式通过组合和委托,可以在运行时动态地为对象加上新的行为

装饰模式的优缺点

优点:

  • 把类中的装饰功能从类中搬移,简化原有类
  • 有效地把类的核心职责和装饰功能区分开
  • 去除相关类中重复的装饰逻辑

缺点:

  • 装饰者模式常常造成设计中出现大量的小类,数据太多可能会使程序变得复杂,对使用API的程序员产生困扰
  • 装饰者在实例化组件时除了实例化组件还要将组件包装进装饰者中,会增加代码复杂度
  • 克服这一缺点,通过工厂模式和生成器模式对实例化部分代码进行封装

什么是适配器模式?

定义:适配器模式将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以一起工作。

适配器通过使用对象组合,以修改的接口包装被适配者。

适配器把被适配者的接口转换为适配者的接口。

适用场景

  1. 需要使用一个现有类而其接口并不符合需求的时候
  2. 当两个类做的事情相同或相似,但具有不同接口时,可以使用适配器模式统一接口

适配器模式代码实例:

//客户所期待的接口
public abstract class Target {
    public void request() {
	System.out.println("普通请求!");
    }
}

//适配器类,通过在内部包装一个Adaptee对象,把原接口转换成目标接口
public class Adapter extends Target {
	//内部的Adaptee对象
    private Adaptee adaptee = new Adaptee();
    @Override
    public void request() {  //适配器类提供的客户端需要的接口
	adaptee.specificRequest();
    }
}

//需要适配的类
public class Adaptee {
    public void specificRequest() {
	System.out.println("特殊的请求!");
    }

}

//适配器客户端
public class AdapterClient {
    public static void main(String[] args) {
	Target target;
	target = new Adapter(); //直接调用适配器类,内部包装了Adaptee
	target.request();
    }
}

适配器模式优缺点

优点:

  • 不需要进行代码的大量改动,通过添加一个适配器类,将改变封装在类中,满足新的需求
  • 让客户和实现的接口/适配器解耦

缺点:

  • 导致更多的“包装”类,用以处理和其他组件的沟通,会导致复杂度和开发时间的增加,并降低运行时的性能
  • 太多的类耦合在一起,会提高维护成本和降低代码的可理解性

适配器模式只有在碰到无法改变原有设计和代码的情况才考虑,在设计开发的过程中,应该预先预防接口不匹配的问题发生,当出现有小的接口不统一时,应及时重构,避免问题的扩大

适配器模式的类型

类适配器:继承被适配者类和目标类

  • 类适配器通过多重继承对一个接口与另一个接口进行匹配

  • 优点:

    • 由于适配器类是适配者类的子类,因此可以再适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
  • 缺点:

    • 对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类
    • 目标抽象类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和他的子类同时适配到目标接口。

对象适配器:利用组合的方式将请求传送给被适配者

  • 可以适配某个类及其任何子类

  • 优点:

    • 把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口。
  • 缺点:

    • 把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口。

与装饰者模式的区别:

  • 装饰者模式:需要添加新的功能时使用

    • 加入新功能到类中,而无需改变现有代码
  • 适配器模式:需要转换接口时使用

    • 允许用户使用新的库和子集合,而无需改变原有代码

最少知识原则(Least Knowledge Principle)又名迪米特法则(Law of Demeter)

  1. 每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元,即只和你的密友谈话

方针:只调用属于以下范围的方法

  1. 该对象本身
  2. 被当做方法的参数而传递进来的对象
  3. 此方法所创建或实例化的任何对象
  4. 对象的任何组件

外观模式(Facade Pattern)

当有很多复杂的接口需要使用时,通过一个类来将复杂的逻辑封装在内并提供简单的统一接口,可以很好的提高代码的可读性,降低程序复杂度。

外观模式就是这样的一个类,不过它并没有“封装”子系统。当你需要简化并统一一个很大的接口或一群复杂的接口时,可以使用外观模式。

什么是外观模式?

定义:外观模式提供了一个统一的接口,用来访问子系统中的一组接口。此模式定义了一个高层接口,使得子系统更容易使用。

  • 可以创建多个外观

适用场景

通过使用外观模式,在数据访问层、业务逻辑层和表示层的层与层之间建立“外观”,降低耦合度

外观模式代码实例:

//“系统”接口,只是标记接口,暂无任何意义
public interface SystemInterface {

}
//子系统类
class SubSystemOne implements SystemInterface {
    public void methodOne() {
		System.out.println("子系统方法一");
    }
}

class SubSystemTwo implements SystemInterface {
    public void methodTwo() {
		System.out.println("子系统方法二");
    }
}

class SubSystemThree implements SystemInterface {
    public void methodThree() {
		System.out.println("子系统方法三");
    }
}

class SubSystemFour implements SystemInterface {
    public void methodFour() {
		System.out.println("子系统方法四");
    }
}
//外观类,包括了所有的子系统的方法或属性,进行组合,以备外界调用
public class Facade {
//子系统
    SubSystemOne subSystemOne;
    SubSystemTwo subSystemTwo;
    SubSystemThree subSystemThree;
    SubSystemFour subSystemFour;

    public Facade() {
        subSystemOne = new SubSystemOne();
        subSystemTwo = new SubSystemTwo();
        subSystemThree = new SubSystemThree();
        subSystemFour = new SubSystemFour();
    }
//外观类提供的统一接口
    public void methodA() {
        System.out.println("方法组A:");
        subSystemOne.methodOne();
        subSystemTwo.methodTwo();
        subSystemFour.methodFour();
    }

    public void methodB() {
        System.out.println("方法组B:");
        subSystemThree.methodThree();
        subSystemFour.methodFour();
    }
}
//外观类客户端
public class FacadeClient {
    public static void main(String[] args) {
        // 由于Facade的作用,客户端可以根本不知道四个子系统的存在
        Facade facade = new Facade();
        facade.methodA();
        facade.methodB();
    }
}

维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,此时可以为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。

外观模式优缺点:

优点:

  • 将客户端从子系统中解耦,客户端代码面向接口编写,若要修改子系统的组件时只需要改动外观类就可以
  • 通过实现一个提供更合理的接口的外观类,隐藏复杂子系统背后的逻辑,提供一个方便使用的接口
  • 没有“封装”子系统的类,只提供简化接口,子系统的类依然可以被调用。在提供简化接口的同时,将系统完整的功能暴露出来,以供需要的人使用。
  • 遵循「最少知识原则

缺点:

  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了「开放-关闭原则

与装饰模式、适配器模式的区别:

  • 装饰者:不改变接口,但加入责任
  • 适配器:将一个接口转成另一个接口,改变接口使其符合客户期望
  • 外观:让接口更简单,提供子系统的一个简化接口

代理模式(Proxy Pattern)

代理从字面意思理解就是代理他人的职务。在计算机中,代理常用来控制和管理访问,如代理服务器,可以作为中转站,代理网络用户去获取网络信息。

什么是代理模式

定义:代理模式为另一个对象提供一个替身或占位符以便控制客户对对象的访问

  • 使用代理模式创建代表,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象

代理控制访问方式:

  1. 远程代理:控制访问远程对象,为一个对象在不同的地理空间提供局部代表
  2. 虚拟代理:控制访问创建开销大的资源,通过代理替代实例化需要很长时间的真实对象(网页加载时的图片框)
  3. 保护代理:基于权限控制对资源的访问

代理模式代码实例:

这里的代理只是简单的示例,实际上的代理往往在上面提到的几个场景(远程、虚拟、保护)中使用,用来控制和管理访问

//接口类,定义真实实体类与代理类共用的接口
public interface Subject {
    public void request();
}
//实体类
public class RealSubject implements Subject {
    @Override
    public void request() {
		System.out.println("真实对象的请求");
    }
}
//代理类
public class Proxy implements Subject {

    // 保存一个引用,使得代理可以访问真实实体
    Subject subject;
    public Proxy() {
		subject = new RealSubject();
    }
    @Override
    public void request() {
		subject.request();
    }

}
//代理客户端
public class ProxyClient {
	public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}

代理模式的优缺点:

优点:

  • 代理模式在访问对象时引入一定程度的间接性,这种间接性可以附加多种用途
  • 代理模式在客户端与目标对象之间起中介作用,可以保护目标对象
  • 使得客户端与目标对象分离,在一定程度上降低了耦合

缺点:

  • 增加了系统的复杂度,客户端只能够看到代理类
  • 会出现大量的重复代码。

与装饰模式、适配器模式的区别

  • 装饰者为对象增加行为,而代理控制对象的访问
  • 代理和适配器都挡在其他对象前面,负责将请求转发给它们
  • 适配器会改变对象适配的接口,而代理则实现相同的接口

代理模式变体类型(简单了解)

  • 防火墙代理(Firewal Proxy):控制网络资源的访问,保护主题免于“坏客户”的侵害
  • 智能引用代理(Smart Reference Proxy):当主题被引用时,进行额外的动作,代理处理另一些事儿。例如计算一个对象被引用的次数
  • 缓存代理(Caching Proxy):为开销大的运算结果提供暂时存储:它也允许多个客户共享结果,以减少计算或网络延迟
  • 同步代理(Synchronization Proxy)在多线程的情况下为主题提供安全的访问
  • 复杂隐藏代理(Complexity Hiding Proxy):用来隐藏一个类的复杂集合的复杂度,并进行访问控制,有时候也称为外观代理(Facade Proxy)
  • 写入时复制代理(Copy-On-Write Proxy):用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止,这是虚拟代理的变体。

总结

对于装饰模式、适配器模式、外观模式和代理模式,他们彼此之间都有相似之处,例如四种都对对象进行了包装,适配器模式和外观模式都提供了接口,代理模式和装饰模式都可能会引入新功能…

但其实区分这几种设计模式重点不在于如何包装类,包装类的个数、是否添加新功能,重点在于不同设计模式的目的(意图)不同

四种设计模式的目的

  • 装饰模式:将一个对象包装起来以增加新的行为和责任

    • 不改变接口,但加入责任
  • 适配器模式:将一个对象包装起来以改变其接口

    • 将一个接口转成另一个接口,改变接口使其符合客户期望
  • 外观模式:将一群对象包装起来以简化其接口

    • 让接口更简单,提供子系统的一个简化接口
  • 代理模式:将一个对象包装起来以控制对它的访问

    • 控制和管理对对象的访问

把握了以上几点,也就记住了四种设计模式的本质区别,相信对于每个设计模式适用场景也能有更深的理解。

关系篇

导语

在上部分中,针对继承在编译时静态决定类,可扩展性差等问题,装饰模式提供了比继承更弹性的方法来为类添加功能函数,这部分进一步讨论继承的潜在问题,重点着眼于类和对象之间的关系桥接模式组合模式享元模式都属于 「结构型设计模式」, 而中介者模式属于 「行为型设计模式」 ,为了更好的几种模式对比而放在这一部分中,尽管关系篇的命名可能不够准确,但还是比较能概括这几种设计模式的共同点。

继承

继承作为面向对象的三个基本特征之一,是最常见的一种类关系,作用如下:

  • 子类通过继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法
  • 或子类从父类继承方法,使得子类具有父类相同的行为。

优点:继承可以使用子类重写父类方法的方式进行扩展,提高了代码的复用性,并获得了一定的可扩展性

缺点:继承的过度使用会导致类的结构过于复杂,对象之间关系太多,代码难以维护,扩展性差

继承关系的潜在问题

  1. 对象的继承关系在编译时静态定义好的,无法在运行时改变从父类继承的实现。
  2. 子类的实现与父类有紧密的依赖关系,父类实现中的任何变化都会导致子类的变化
  3. 复用子类时继承的实现可能不适合解决新的问题,这时父类必须重写或替换,这种依赖关系限制了灵活性和复用性

子类和父类之间的关系是一种高耦合关系。

桥接模式(Bridge Pattern)

一个父类可以通过子类实现多种变化。在类变化较为复杂的情况下,只使用继承会造成大量的类增加,不能满足开放-封闭原则。继承是一种强耦合的结构,我们的设计目标是找到一个弱耦合、松耦合的结构来表示抽象类和实现之间的关系,这个设计模式就是桥接模式

桥接模式通过解耦类不同方向的变化,使用对象组合的方式,把两个角色之间继承关系改为组合关系,从而使得两者应对各自独立的变化。

什么是桥接模式?

顾名思义,「桥」 起到连接作用,在桥接模式中,通过 「桥」 连接的抽象和实现两个独立的结构,这两个部分可以以继承的方式独立扩展、变化而不相互影响。

定义:桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。

  • 抽象与实现分离,不是说抽象类和派生类分离
  • 实现指的是抽象类和它的派生类用来实现自己的对象,实现可以有多角度(方向)分类

桥接模式的核心意图:将每一种分类的实现分离出来,让他们独立变化,减少它们之间的耦合,每种实现的变化不会影响其他实现,从而达到应对变化的目的。

桥接模式如何应对变化?

  • 找出变化封装之
  • 优先使用对象聚集,而不是继承,即合成/聚合复用原则

合成/聚合复用原则(CARP)

尽量使用合成/聚合,尽量不要使用类继承

合成(Composition):表示一种强“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样

聚合(Aggregation):表示一种弱“拥有”关系,体现A对象包含B对象,但B对象不是A对象的一部分

合成和聚类都是关联的特殊种类。

好处:

  • 有限使用对象的合成/聚合将有助于保持每个类被封装,并被集中在单个任务上。
  • 这样类和类继承层次都可以保持较小规模,不至于增长过多,而不可控制。

桥接模式示意图

假设现在有一个书本生产系统,生产的书本有Book1、Book2两种类型,黄色和绿色两种不同颜色,现有抽象类Book,若仅使用继承,那么就需要如下图示的四种子类才可以满足需求。

若要添加书本类型或颜色类型,均需要声明对子类进行新的扩展,需要的子类数将呈现爆炸性增长,代码复杂度将不断增加。

仅使用继承:

img

而使用桥接模式,可以将书本类型(抽象)和颜色类型(实现)两部分分离开来,改继承为组合,扩展和变化将在两个独立角度中进行,不再需要通过添加子类的方式实现扩展。

桥接模式:

img

颜色相关的代码将被抽取到颜色类中,并通过在书本类中添加指向颜色对象的成员变量,将书本和颜色两部分连接起来。使用桥接模式后新增颜色将不会对现有的形状类产生影响,反之新增形状也不会对现有的颜色类产生影响,新增功能或方法都只需要扩展类即可,不需要修改现有类,符合“开放-封闭”原则。

桥接模式代码实例:

public abstract class Abstraction {
    protected Implementor implementor;// 桥接模式的关键,使得Abstraction聚合Implementor
    private String name;

    public Abstraction(String name) {
		this.setName(name);
    }
	//抽象类所聚合的实现类
    public void setImplementor(Implementor implementor) {
		this.implementor = implementor;
    }
	//相关方法
    public void operation() {
		System.out.print("Abstraction-" + this.getName() + ": ");
		implementor.operation();
    }
    public String getName() {
		return name;
    }
    public void setName(String name) {
		this.name = name;
    }
}
//抽象实现类A
class AbstractionA extends Abstraction {
    public AbstractionA(String name) {
		super(name);
    }
    @Override
    public void operation() {
		super.operation();
    }
}
//抽象实现类B
class AbstractionB extends Abstraction 
    public AbstractionB(String name) {
		super(name);
    }
    @Override
    public void operation() {
		super.operation();
    }
}
public abstract class Implementor {
    public abstract void operation();
}

class ConcreteImplemtorA extends Implementor {
    @Override
    public void operation() {
		System.out.println("ConcreteImplemtorA的方法执行");
    }
}

class ConcreteImplemtorB extends Implementor {
    @Override
    public void operation() {
		System.out.println("ConcreteImplemtorB的方法执行");
    }
}
//客户端
public class BridgeClient {
    public static void main(String[] args) {
    	Abstraction a = new AbstractionA("A");//抽象类A实例
    	a.setImplementor(new ConcreteImplemtorA());//设置包含的具体实现类
    	a.operation();
    	a.setImplementor(new ConcreteImplemtorB());
    	a.operation();
    		
    	Abstraction b = new AbstractionB("B");//抽象类B实例
    	b.setImplementor(new ConcreteImplemtorA());
    	b.operation();
    	b.setImplementor(new ConcreteImplemtorB());
    	b.operation();
    }
}

桥接模式的优缺点

优点:

  • 实现了抽象和实现的分离,减少了它们之间的耦合
  • 多角度分类实现对象可以降低项目的复杂度和类之间的关系,不同角度之间不会相互影响
  • 新增功能或方法都只需要扩展类即可,不需要修改现有类, 符合“开放-封闭”原则, 提高了类的可扩展性

缺点:

  • 桥接模式的引入会增加系统的复杂度,增加了代码的理解和设计难度
  • 桥接模式的使用范围有一定局限性,需要识别出系统中两个或多个独立变化的维度才能使用。

组合模式(Composite Pattern)

什么是组合模式?

定义:将对象组合成树形结构以表示 “部分-整体” 的层次结构,组合模式能让用户以一致的方法处理单个对象和组合对象。

  • 使用树形结构管理对象
  • 能够在单个对象之间和组合对象之间游走
  • 基本对象可以被组合成更复杂的组合对象,组合对象又可以被组合,代码中的所有使用基本对象都可以使用组合对象

组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。任何用到基本对象的地方都可以使用组合对象,客户可以一致地使用组合结构和单个对象。

组合、组件、叶节点、枝节点

组合由组件构成,组合结构中的任意对象称为组件,组件有两种类型:

  • 叶节点元素
  • 枝节点元素(组合类)

组合的树形结构:

根是顶层的组合,往下是枝节点,最末端是叶节点

  • 在组合模式中所有的对象都属于同一个类,整体与部分可以被一致对待
  • 使用组合结构,我们能把相同的操作应用到组合和个别对象上,在大多数情况下,我们可以忽略对象组合和单个对象之间的差别

组合模式的分类

透明方式

在组件接口中声明所有用来管理子对象的方法,使子对象(叶节点和枝节点)对于外界没有区别,具备完全一致的行为接口

透明性:组合模式以单一责任设计原则换取“透明性”,组件的接口同时包含一些管理子节点和叶节点的操作,无论是子节点还是叶节点对用户而言都是透明的

存在的问题:

  • 存在叶节点类不具有相关的方法,实现没有意义的方法的问题,违背了单一职责原则,即最少知识原则
  • 用户有可能做一些不恰当或没有意义的操作,失去一些安全性

安全方式

在组合对象中实现所有用来管理子对象的方法,组件类中不声明相关方法,客户端调用时需要做出相应的判断

安全性: 将责任进行区分,放在不同的接口上,代码需要通过条件语句等判断处理不同类型的节点,失去了透明性,但这样的设计比较安全

安全方式符合设计模式的的单一职责原则和接口隔离原则

存在的问题:

  • 客户端需要对枝节点和子节点进行区分,才能处理不同层次的操作,无法依赖抽象,违背了设计模式的依赖倒置原则。

总结:在实现组合模式时,需要根据需要平衡透明性和安全性。

组合模式代码实例:

/**
 * Component为组合中的对象提供统一的接口,在适当情况下,实现所有类共有接口的默认行为。
 * 使用组合结构的所有类都继承该类
 */
public abstract class Component {
    protected String name;

    public Component(String name) {
		this.name = name;
    }

    public abstract void add(Component component);

    public abstract void remove(Component component);

    public abstract void display(int depth);

}
// 组合类(枝节点),用来存储子部件,实现了组件接口中的相关操作
public class Composite extends Component {
    private List<Component> children = new ArrayList<Component>();

    public Composite(String name) {
		super(name);
    }

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

    @Override
    public void remove(Component component) {
		children.remove(component);
    }

    @Override
    public void display(int depth) {
        // 显示其枝节点名称,并对其下级进行遍历
        System.out.println(StringUtil.repeatableString("-", depth) + this.name);		
        for (Component component : children) {
            component.display(depth + 2);
        }
    }
}
//叶节点类,组合模式中的最小粒度,没有子节点
public class Leaf extends Component {

    public Leaf(String name) {
		super(name);
    }

    @Override
    public void add(Component component) {
		System.out.println("cannot add to a leaf");
    }

    @Override
    public void remove(Component component) {
		System.out.println("cannot remove from a leaf");
    }

    @Override
    public void display(int depth) {
		// 通过“-”的数目显示级别
		System.out.println(StringUtil.repeatableString("-", depth) + this.name);
    }

}
public class CompositeClient {
	// 展现了组合结构的构成过程
    public static void main(String[] args) {
        // 生成树根,根上长出两叶Leaf A和Leaf B
        Composite root = new Composite("root");
        root.add(new Leaf("Leaf A"));
        root.add(new Leaf("Leaf B"));

        // 根上长出分支Composite X,分支上也有两叶Leaf X-A和Leaf X-B
        Composite compositeX = new Composite("Composite X");
        compositeX.add(new Leaf("Leaf X-A"));
        compositeX.add(new Leaf("Leaf X-B"));
        root.add(compositeX);

        // 在Composite X上再长出分支Composite X-Y,分支上也有两叶Leaf X-Y-A和Leaf X-Y-B
        Composite compositeXY = new Composite("Composite X-Y");
        compositeXY.add(new Leaf("Leaf X-Y-A"));
        compositeXY.add(new Leaf("Leaf X-Y-B"));
        compositeX.add(compositeXY);

        // 显示大树的样子
        root.display(1);
    }
}

何时使用组合模式

  1. 需求中体现部分与整体层次的结构时,希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式
  2. 当程序中有多个对象集合,且对象之间有“整体/部分”关系,并且想用一致方式处理这些对象时,需要组合模式

组合模式的优缺点

优点:

  • 用户不需要关心对象是组合对象,组合模式可以让用户一致地使用组合结构和单个对象
  • 组合模式中叶节点的添加十分方便,符合开放-关闭原则,便于维护

缺点:

  • 叶节点和枝节点都是实现类,而不是接口,违反了依赖倒置原则
  • 组合类的引入提升了设计的复杂度,客户端需要花时间理清类之间的层次关系

享元模式(Flyweight Pattern)

在程序设计中,有时需要使用大量对象,但使用很多对象会导致的内存开销过大的问题,若使用的对象大量都是重复,则会造成资源的浪费。享元模式是针对这一问题提出的,可以避免大量相似类的开销

  • 如果这些实例除了几个参数外基本上都是相同的时,就能够大幅度地减少需要实例化的类的数量。
  • 如果能把那些参数移到类实例的外面,在方法调用时把它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

什么是享元模式?

运用共享技术有效地支持大量细粒度的对象

  • 本质:缓存共享对象,降低内存消耗。
  • 享元模式属于工厂方法模式的改进

享元对象的状态分类

内部状态

在享元对象内部且不会随环境改变而改变的共享部分

外部状态

随着环境改变而改变,不可以共享的状态

享元模式的主要角色:

享元工厂(Flyweight Factory)

用来创建并管理Flyweight对象,主要用来确保合理地共享Flyweight,当用户请求一个Flyweight时,为对象提供一个已创建的实例,若不存在则为对象创建一个新的Flyweight实例

抽象享元类(Flyweight)

所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态

具体享元类(Concrete Flyweight)

继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间

非享元类(Unshared Concrete Flyweight )

指不需要共享的Flyweight子类

享元模式代码实例:

//享元超类
public abstract class FlyWeight {
	//接受并作用于外部状态的方法
    public abstract void operation(int extrinsicState);
}

class ConcreteFlyWeight extends FlyWeight {
    @Override
    public void operation(int extrinsicState) {
		System.out.println("具体FlyWeight:" + extrinsicState);
    }
}

class UnsharedConcreteFlyWeight extends FlyWeight {

    @Override
    public void operation(int extrinsicState) {
		System.out.println("不共享的具体FlyWeight:" + extrinsicState);
    }
}
public class FlyWeightFactory {
    private HashMap<String, FlyWeight> flyWeights = new HashMap<String, FlyWeight>();

    public FlyWeight getFlyWeight(String key) {
        if (!flyWeights.containsKey(key)) {
            flyWeights.put(key, new ConcreteFlyWeight());
        }
        return flyWeights.get(key);
    }
}
public class FlyWeightClient {
    public static void main(String[] args) {
        int extrinsicState = 22;
        FlyWeightFactory f = new FlyWeightFactory();

        FlyWeight fx = f.getFlyWeight("X");
        fx.operation(--extrinsicState);

        FlyWeight fy = f.getFlyWeight("Y");
        fy.operation(--extrinsicState);

        FlyWeight fz = f.getFlyWeight("Z");
        fz.operation(--extrinsicState);

        FlyWeight uf = new UnsharedConcreteFlyWeight();
        uf.operation(--extrinsicState);
    }
}

什么时候使用享元模式

  • 当一个应用程序使用了大量的对象,而这些大量对象产生了巨大的存储开销时应该考虑使用享元模式
  • 对象的大多数状态可以使用外部状态,删除对象的外部状态可以用相对较少的共享对象取代很多组对象时,可以考虑使用享元模式

享元模式的优缺点

优点:

  • 使用享元模式中共享对象的使用可以大大减少对象实例总数,节约存储开销,节约量随着共享状态的增多而增大
  • 避免大量细粒度对象的使用,又不影响程序,运用共享技术有效地支持大量细粒度的对象

缺点:

  • 需要维护一个记录了系统已有的所有享元的列表,列表也需要耗费资源
  • 享元模式会增加系统的复杂度,对象共享需要使一些状态外部化,会增加程序的逻辑复杂度,在程序中有足够多的的对象实例可供共享时才值得使用享元模式

享元模式的应用

  1. NET中字符串string使用了享元模式,对于相同的字符串对象使用同一个实例,用引用指向的方式实现字符串的内存共享
  2. 棋类游戏中,棋子的颜色是内部状态,位置是外部状态,以围棋为例,使用享元模式,仅声明黑白两个棋子实例即可,大大减少了内存开销

中介者模式(Mediator Pattern)

当程序设计中使用大量对象时,对象之间的关联关系和系统代码逻辑复杂,可扩展性差,不利于应对变化。中介者模式通过对对象集体行为的封装来避免这个问题。

什么是中介者模式?

定义:用一个中介对象来封装一系列的对象交互。中介者使个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

  • 中介者模式容易在系统中应用,也容易误用。在考虑使用中介者模式前,一定得考虑好系统设计的合理性

中介者

中介者模式将集体行为封装成一个独立的中介者对象

  • 中介者负责控制和协调一组对象间的交互,充当中介是的注重的对象不再相互显式引用,对象只知道中介者,减少了相互连接的数量。类之间的耦合度降低,有利于复用。

同事类

实现业务的具体类,各同事类之间的行为均通过中介者进行控制、协调。

中介者模式的关系图

多对象关系图:

img

中介者模式对象关系图:

img

中介者模式使得对象均只需与中介者沟通,对象之间的关系转变为星型结构,能有效地减少系统的耦合

中介者模式与桥接模式:

  • 桥接模式,把一个类里面多对多的关系,转化为类外部的多个类之间的多对一关系。
  • 中介模式,把多个类的多对多的关系,转化为一对多的关系。从而让代码的职责更为单一,更利于复用、扩展、测试。

中介者模式代码实例:

//抽象中介类
public abstract class Mediator {
    public abstract void send(String message, Colleague colleague);
}
//具体中介类
class ConcreteMediator extends Mediator {
    // 需要了解所有的具体同事对象
    private ConcreteColleague1 c1;
    private ConcreteColleague2 c2;

    public ConcreteColleague1 getC1() {
		return c1;
    }
    public void setC1(ConcreteColleague1 c1) {
		this.c1 = c1;
    }

    public ConcreteColleague2 getC2() {
		return c2;
    }
    public void setC2(ConcreteColleague2 c2) {
		this.c2 = c2;
    }

    @Override
    public void send(String message, Colleague colleague) {
        // 重写发送信息的方法,根据对象做出选择判断,通知对象
        if (colleague == c1) {
            c2.notifyMsg(message);
        } else {
            c1.notifyMsg(message);
        }
    }
}
//抽象同事类
public abstract class Colleague {

    protected Mediator mediator;
    public Colleague(Mediator mediator) {
		this.mediator = mediator;
    }
    public abstract void sendMsg(String message);
    public abstract void notifyMsg(String message);
}
//具体同事类
class ConcreteColleague1 extends Colleague {

    public ConcreteColleague1(Mediator mediator) {
		super(mediator);
    }

    @Override
    public void sendMsg(String message) {
		mediator.send(message, this);
    }

    @Override
    public void notifyMsg(String message) {
		System.out.println("同事1得到消息:" + message);
    }

}

class ConcreteColleague2 extends Colleague {

    public ConcreteColleague2(Mediator mediator) {
		super(mediator);
    }

    @Override
    public void sendMsg(String message) {
		mediator.send(message, this);
    }

    @Override
    public void notifyMsg(String message) {
		System.out.println("同事2得到消息:" + message);
    }
}
public class MediatorClient {
    public static void main(String[] args) {
        ConcreteMediator concreteMediator = new ConcreteMediator();

        // 让两个具体同事类认识中介者对象
        ConcreteColleague1 concreteColleague1 = new ConcreteColleague1(
            concreteMediator);
        ConcreteColleague2 concreteColleague2 = new ConcreteColleague2(
            concreteMediator);

        // 让中介者认识各个具体同事类对象
        concreteMediator.setC1(concreteColleague1);
        concreteMediator.setC2(concreteColleague2);

        // 具体同事类对象的消息发送都是通过中介者对象转发
        concreteColleague1.sendMsg("吃过饭了没有?");
        concreteColleague2.sendMsg("没有呢,你打算请客?");
    }
}

中介者模式的优缺点

优点:

  • 减少各个对象实例之间的耦合,可以独立改变和复用各个与中介者关联的对象实例
  • 将对象之间的协作进行抽象,将中介作为一个独立的概念封装在对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统

缺点:

  • 中介者模式的优点来自集中控制,缺点也来自集中控制

  • 中介者实现了控制集中化,将交互复杂性转变为中介者的复杂性,当同事类越多,中介者的业务就越复杂,代码会变得难以管理和改动。

中介者模式的应用:

中介者模式一般应用于一组对象以定义良好但复杂的方式进行通信的场合,以及想定制一个分布在多个类中的行为,而不想生成太多子类的场合

  • .NET中Windows应用程序的Form,Web网站程序的aspx都使用了中介者模式

总结

桥接模式、组合模式和中介者模式都改变类或对象之间的关系,从而达到各自减少对象数量或降低代码复杂度的目的。

  • 桥接模式使用合成/聚合复用原则,变继承为聚合, 将抽象部分与它的实现部分分离, 减少了子类数量,降低了代码的耦合度

  • 组合模式用树形结构表示 “部分-整体” 的层次结构来管理对象,使得用户可以用统一的接口使用组合结构中的所有对象,简化了代码,提高了可扩展性

  • 中介者模式使用中介者负责控制和协调一组对象间的交互,将多对多的关系转为一对多降低了对象之间的耦合度,提升了代码的复用性、可扩展性。

  • 享元模式则主要为了解决内存资源浪费问题,通过对对象之间相似部分的抽取,利用共享技术,减少对象实例。

区分这几种设计模式的关键,依旧在于把握目的(意图)的不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/86781.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手搭手入门MyBatis-Plus

MyBatis-Plus Mybatis-Plus介绍 为简化开发而生 MyBatis-Plus(opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis(opens new window) 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 特性 无侵入&#…

protobuf+netty自定义编码解码

protobufnetty自定义编 项目背景 protobufnetty自定义编码解码 比如心跳协议&#xff0c;客户端请求的协议是10001&#xff0c;在java端如何解码&#xff0c;心跳返回协议如何编码&#xff0c;将协议号带过去 // 心跳包 //10001 message c2s_heartbeat { }//10002 message …

LeetCode--HOT100题(38)

目录 题目描述&#xff1a;226. 翻转二叉树&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;226. 翻转二叉树&#xff08;简单&#xff09; 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 LeetCode做题链…

lvs-DR模式:

lvs-DR数据包流向分析 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路…

7-42 整型关键字的散列映射

题目链接&#xff1a;这里 题目大意&#xff1a;就是写一个线性探测的散列 然鹅&#xff0c;我不会写(?)我一共错了两个地方 有冲突的情况下&#xff0c;就是线性探查然后往后找&#xff0c;但是我之前写的是t&#xff0c;应该是t (t1)%p;…在有重复关键字的时候&#xff0c…

大学生创业出路【第二弹】科创训练营

目录 &#x1f680;一、我从哪里了解到的训练营 &#x1f680;二、训练营里学习和日常 &#x1f50e;学习 &#x1f50e;环境和设备 &#x1f50e;遇到的人 &#x1f50e;团队记录视频 &#x1f680;三、感悟 ​​​​个人主页&#xff1a;一天三顿-不喝奶茶&#x1f39…

UE4/5Niagara粒子特效之Niagara_Particles官方案例:1.5->2.3

目录 之前的文章&#xff1a; 1.5 Blend Attributes by Value 发射器更新 粒子生成 粒子更新 2.1 Static Beams ​编辑 发射器更新&#xff1a; 粒子生成 粒子更新 2.2 Dynamic Beams 没有开始模拟前的效果是&#xff1a; 开始模拟后的效果是&#xff1a; 发射器更新 …

数据结构入门 — 顺序表详解

前言 数据结构入门 — 顺序表详解 博客主页链接&#xff1a;https://blog.csdn.net/m0_74014525 关注博主&#xff0c;后期持续更新系列文章 文章末尾有源码 *****感谢观看&#xff0c;希望对你有所帮助***** 文章目录 前言一、顺序表1. 顺序表是什么2. 优缺点 二、概念及结构…

java-IONIO

一、JAVA IO 1.1. 阻塞 IO 模型 最传统的一种 IO 模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后&#xff0c;内 核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…

java八股文面试[数据结构]——ArrayList和LinkedList区别

ArrayList和LinkedList的异同 二者的线程都不安全&#xff0c;相对线程安全的Vector,执行效率高。此外&#xff0c;ArrayList时实现了基于动态数组的数据结构&#xff0c;LinkedList基于链表的数据结构&#xff0c;对于随机访问get和set&#xff0c;ArrayList觉得优于LinkedLis…

线性回归的正则化改进(岭回归、Lasso、弹性网络),最小二乘法和最大似然估计之间关系,正则化

目录 最小二乘法 极大似然估计的思想 概率&#xff1a;已知分布参数-对分布参数进行估计 概率描述的是结果;似然描述的是假设/模型​编辑 似然&#xff1a;已知观测结果-对分布参数进行估计​编辑 对数函数消灭连乘-连乘导致算法参数消失 极大似然估计公式&#xff1a;将乘…

LeetCode:Hot100python版本之回溯

回溯算法其实是纯暴力搜索。for循环嵌套是写不出的 组合&#xff1a;没有顺序 排列&#xff1a;有顺序 回溯法可以抽象为树形结构。只有在回溯算法中递归才会有返回值。 46. 全排列 排列是有顺序的。 组合类问题用startindex&#xff0c;排序类问题用used&#xff0c;来标…

【网络】DNS | ICMP | NAT | 代理服务器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 前面几篇文章虽然讲介绍了整个网络通信的协议栈&#xff0c;我们也知道了完整的网络通信过程&#xff…

【图像去噪】基于混合自适应(EM 自适应)实现自适应图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

如何拉取Gitee / GitHub上的Unity项目并成功运行

前言 由于目前大部分人使用的仓库都是Gitee或者是GitHub&#xff0c;包括小编的公司所使用的项目仓库也包括了Gitee&#xff1b;我们需要学习技术栈时都会去百度或者是去GitHub上看看别人的项目观摩学习&#xff0c;可能很多小白在遇到拉取代码时出现各种问题&#xff0c;或者…

Server2016安装SQL server数据库遇到异常解决

首先看几个会出现的异常&#xff0c;下边看解决办法&#xff1a; 第一步: 先修改安装包x86\setup目录下的setupsql.exe,以Xp&#xff0c;SP3兼容模式运行&#xff0c; 这个右键&#xff0c;属性&#xff0c;兼容性&#xff0c;修改就行&#xff0c;类似这样 第二步: 修改c:…

【Rust】Rust学习 第十六章无畏并发

安全且高效的处理并发编程是 Rust 的另一个主要目标。并发编程&#xff08;Concurrent programming&#xff09;&#xff0c;代表程序的不同部分相互独立的执行&#xff0c;而 并行编程&#xff08;parallel programming&#xff09;代表程序不同部分于同时执行&#xff0c;这两…

【优选算法】—— 字符串匹配算法

在本期的字符串匹配算法中&#xff0c;我将给大家带来常见的两种经典的示例&#xff1a; 1、暴力匹配&#xff08;BF&#xff09;算法 2、KMP算法 目录 &#xff08;一&#xff09;暴力匹配&#xff08;BF&#xff09;算法 1、思想 2、演示 3、代码展示 &#xff08;二&…

大数据课程K2——Spark的RDD弹性分布式数据集

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的RDD结构; ⚪ 掌握Spark的RDD操作方法; ⚪ 掌握Spark的RDD常用变换方法、常用执行方法; 一、Spark最核心的数据结构——RDD弹性分布式数据集 1. 概述 初学Spark时,把RDD看…

【微服务】spring 条件注解从使用到源码分析详解

目录 一、前言 二、spring 条件注解概述 2.1 条件注解Conditional介绍 2.2 Conditional扩展注解 2.2.1 Conditional扩展注解汇总 三、spring 条件注解案例演示 3.1 ConditionalOnBean 3.2 ConditionalOnMissingBean 3.2.1 使用在类上 3.2.2 使用场景补充 3.3 Condit…