设计模式八股
设计模式
-1.设计原则
1.单一职责原则
单一职责原则:一个类或者模块只负责完成一个职责(或者功能)
一个类只负责完成一个职责或者功能。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。换个角度来讲就是,一个类包含了两个或者两个以上业务不相干的功能,那我们就说它职责不够单一,应该将它拆分成多个功能更加单一、粒度更细的类。
评价一个类的职责是否足够单一,我们并没有一个非常明确的、可以量化的标准,可以说,这是件非常主观、仁者见仁智者见智的事情。实际上,在真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。所以, 我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构(后面的章节中我们会讲到)。
2.开闭原则
“对扩展开放、对修改关闭”。添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
怎么实现“对扩展开放、对修改关闭”?
在写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩展开放、对修改关闭”。
我们还要善于识别代码中的可变部分和不可变部分,把可变部分封装
0.设计模式的划分
1.1 根据模式的目的划分
根据模式是用来完成什么样的工作来划分,这种方法可分为创建型模式、结构型模式、行为型模式3种。
1.1.1 创建型设计模式
用于描述“怎么创建对象”。它的主要特点是“将对象的创建与使用分离”。如,单例、原型、工厂方法、抽象工厂、建造者等5种创建型模式。
创建型设计模式主要解决对象的创建问题,封装复杂的创建过程,以及解耦对象的创建代码和使用代码。其中,单例模式用来创建全局唯一的对象;工厂模式用来创建类型不同但相关的对象(继承同一父类或接口的一组子类),由给定的参数来决定创建哪种类型的对象:建造者模式用来创建复杂的对象,可以通过设置不同的可选参数,定制化地创建不同的对象;
1.1.2 结构型模式
用于描述“如何将类或对象按某种布局组成更大的结构”。如,代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式。
1.1.3 行为型模式
用于描述“类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责”。如,模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录模式、解释器等11中行为模式。
1.2 根据模式的作用划分
根据模式的主要用于类上还是主要用户对象上来分,这种方式可分为类模式和对象模式两种。
1.2.1 类模式
用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时便确定下来了。如,工厂方法、(类)适配器、模板方法、解释器等4种类模式。
1.2.2 对象模式
用户处理对象之间关系的,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。
类模式 | 工厂方法 | (类)适配器 | 模板方法 解释器 |
---|---|---|---|
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象)适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
1. 工厂方法模式
1.什么是工厂模式?
工厂模式是一种创建型设计模式,它通过定义一个创建对象的接口来封装实例化对象的行为,让子类决定实例化哪一个类。
工厂模式的概念来源于传统的工厂生产流程,其主要目的是将对象的创建和使用分离,使得客户端不需要知道具体的产品类,只需通过工厂请求所需的产品即可。这样做的好处在于增加了系统的灵活性和可维护性,同时能够降低代码的耦合度。
2.工厂模式的具体作用
具体到工厂模式的作用,主要体现在以下几个方面:
-
解耦对象创建和使用过程:客户端代码不直接调用构造函数来创建对象,而是通过工厂类的接口来获取所需的对象,这样可以减少客户端与具体类之间的依赖关系。
-
降低重复代码:如果多个地方需要创建同一个复杂对象,可以通过工厂方法统一管理,减少重复的代码,并便于后期维护。
-
增强系统的扩展性:当系统需要新增产品时,只需要扩展新的工厂类而不用修改原有代码,符合开闭原则。
-
隐藏具体实现:客户端只关心产品的接口而不是具体的实现,使得在不改变客户端代码的情况下更换或者升级产品成为可能。
-
提高代码的可管理性和灵活性:通过工厂模式,可以方便地管理和切换不同的产品实现,适应业务需求变化。
总之,工厂模式不仅有助于构建清晰、易于维护和扩展的代码结构,而且能够在多产品或变体间提供灵活的切换机制,是面向对象设计中常用的一种高效设计模式。
将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。封装复杂的创建逻辑,调用者无需了解如何创建对象。
3.工厂模式的优缺点
工厂模式,特别是在软件工程中,主要目的是创建对象,同时将对象的创建过程和使用过程解耦。它包括几种不同的变体,如简单工厂、工厂方法、抽象工厂等。
工厂模式的优点主要包括:
-
封装性:客户端代码不需要知道如何创建所需的对象,只需传递正确的参数即可获取需要的对象。
-
灵活性和可维护性:当添加新产品时,只需扩展工厂类而不必修改客户端代码,这符合开闭原则,即对扩展开放,对修改封闭。
-
隔离变化:工厂模式把对象创建过程中易变的部分隔离起来,有助于控制变化。
然而,工厂模式也有一些缺点:
-
复杂性增加:由于引入了工厂类(在简单工厂模式中),系统的复杂性有所增加。尤其是在简单工厂模式中,如果产品类层次结构很复杂,那么工厂类的职责会变得沉重,因为它需要包含所有产品的创建逻辑。
-
违反单一职责原则:简单工厂模式可能会违反单一职责原则,因为工厂类既要负责创建对象,又要包含业务逻辑判断。
-
扩展困难:每次新增或者删除产品时,可能需要修改工厂类的代码,尤其是在简单工厂模式中,这一点尤为明显。
综上所述,工厂模式通过封装和隔离对象创建的细节,提供了一种灵活且易于维护的方式来管理对象的生命周期。但同时,它也增加了系统的复杂性并可能限制了其扩展性。在应用工厂模式时,应根据具体场景仔细考量这些优缺点,以实现最佳的设计决策。
单一职责原则
单一职责原则(Single Responsibility Principle,简称SRP)是面向对象编程中的一个重要设计原则。它的核心思想是:一个类或者模块应该只负责一项职责,如果有多个职责,就应该拆分成多个类或模块。
这个原则的主要目的是为了降低代码的复杂性,提高代码的可读性和可维护性。如果一个类承担了过多的职责,那么当其中一个职责发生变化时,可能会影响到其他的职责,这样就增加了代码的耦合度,降低了代码的可维护性。
例如,假设有一个类叫做“Employee”,它既负责处理员工的工资计算,又负责处理员工的考勤记录。那么当工资计算的规则发生变化时,可能会影响到考勤记录的功能,反之亦然。如果我们按照单一职责原则来设计,就应该将“Employee”类拆分为两个类,一个负责工资计算,一个负责考勤记录,这样就可以降低代码的耦合度,提高代码的可维护性。
开闭原则
开闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
4.3种工厂模式
三种工厂
-
简单工厂模式
-
工厂方法模式
-
抽象工厂模式
1.2 简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯。把对象的
1.2.1 结构创建交给工厂类
简单工厂包含如下角色:
-
抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
-
具体产品 :实现或者继承抽象产品的子类
-
具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
1.2.2 实现
现在使用简单工厂对上面案例进行改进,类图如下:
interface AbstractProduct{ public void dosomething(); } class ConcretProduct1 implements AbstractProduct{ @Override public void dosomething() { System.out.println("1"); } } class ConcretProduct2 implements AbstractProduct{ @Override public void dosomething() { System.out.println("2"); } } //具体工厂 class Factory{ public AbstractProduct createProduct(String type){ AbstractProduct product = null; if("concretProduct1".equals(type)){ product = new ConcretProduct1(); }else if("concretProduct2".equals(type)){ product = new ConcretProduct2(); } return product; } } public class Test { //get方法必须要是static修饰的 public static AbstractProduct getProduct(String type){ Factory factory = new Factory(); AbstractProduct product = factory.createProduct(type); return product; } public static void main(String[] args) { AbstractProduct product = getProduct("concretProduct2"); product.dosomething(); } }
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类(具体产品)的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
1.2.3 优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
1.3 工厂方法模式
针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
1.3.1 概念
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。具体工厂只用来创建具体产品
1.3.2 结构
工厂方法模式的主要角色:
-
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
-
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
-
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
-
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
1.3.3 实现
使用工厂方法模式对上例进行改进,类图如下:
流程:
代码如下:
interface AbstractProduct{ public void dosomething(); } class ConcretProduct1 implements AbstractProduct{ @Override public void dosomething() { System.out.println("1"); } } class ConcretProduct2 implements AbstractProduct{ @Override public void dosomething() { System.out.println("2"); } } interface AbstractFactory{ public AbstractProduct createProduct(); } //对应的具体工厂 class ConcretProduct1Factory implements AbstractFactory{ @Override public AbstractProduct createProduct() { return new ConcretProduct1(); } } class ConcretProduct2Factory implements AbstractFactory{ @Override public AbstractProduct createProduct() { return new ConcretProduct2(); } } public class Test { private static AbstractFactory factory; //构造函数指定某个工厂 public Test(AbstractFactory factory){