六大设计原则
Java设计模式-六大设计原则(https://cloud.tencent.com/developer/article/1798119)
设计模式是解决问题的思想,重要的是思想,而不是刻意在写代码时去追求设计模式
单一功能原则
单一功能原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。
这个术语由罗伯特·C·马丁(Robert Cecil Martin)在他的《敏捷软件开发,原则,模式和实践》一书中的一篇名为〈面向对象设计原则〉的文章中给出。 [1] 马丁表述该原则是基于的《结构化分析和系统规格》[2]一书中的内聚原则(Cohesion)上。
马丁把功能(职责)定义为:“改变的原因”,并且总结出一个类或者模块应该有且只有一个改变的原因。
一个具体的例子就是,想象有一个用于编辑和打印报表的模块。这样的一个模块存在两个改变的原因。第一,报表的内容可以改变(编辑)。第二,报表的格式可以改变(打印)。这两方面会的改变因为完全不同的起因而发生:一个是本质的修改,一个是表面的修改。单一功能原则认为这两方面的问题事实上是两个分离的功能,因此他们应该分离在不同的类或者模块里。把有不同的改变原因的事物耦合在一起的设计是糟糕的。
保持一个类专注于单一功能点上的一个重要的原因是,它会使得类更加的健壮。继续上面的例子,如果有一个对于报表编辑流程的修改,那么将存在极大的危险性,因为假设这两个功能存在于同一个类中,修改报表的编辑流程会导致公共状态或者依赖关系的改变,打印功能的代码会因此不工作。
比如相机,只有拍照的功能
里氏替换原则(Liskov Substitution Principle)
简单来说就是定义了什么是父子。
- 里氏替换原则中,子类可以扩展父类的功能,但不要改变父类原有的功能,对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
- 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代替o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。(If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.)
- 所有引用基类的地方必须能透明地使用其子类的对象。(Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.) 只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程。
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的
- 接口或抽象类不依赖于实现类
- 实现类依赖于接口或抽象类
接口隔离原则(Interface Segregation Principle)
指明客户(client)不应被迫使用对其而言无用的方法或功能。
接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口(role interfaces)。
接口隔离原则(ISP)的目的是系统解开耦合,从而容易重构,更改和重新部署。
在业务中,service功能。
迪米特法则(Law of Demeter)
一个对象应该对其他对象有最少的了解 迪米特法则对低耦合提出了明确的要求
- Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. (每个单元对于其他的单元只能拥有有限的知识:只是与当前单元紧密联系的单元)
- Each unit should only talk to its friends; don’t talk to strangers. (每个单元只能和它的朋友交谈:不能和陌生单元交谈)
- Only talk to your immediate friends. (只和自己直接的朋友交谈)
朋友类的定义:出现在成员变量、方法的输入输出参数中的类是成员朋友类,出现在方法体内部的类不是
迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合之后,类的复用率才可以提高
开闭原则(The Open/Closed Principle)
规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”[1],这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用品质的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。
开闭原则的命名被应用在两种方式上。这两种方式都使用了继承来解决明显的困境,但是它们的目的,技术以及结果是不同的。
开闭原则是一个非常基础的原则,其他的五个原则都是开闭原则的具体,也就是说其他的五个原则是指导设计的工具和方法,而开闭原则才是它们的精神领袖。只要我们遵守好其他的五大原则,那么我们设计的软件自然就遵守了开闭原则。
简单总结上面的五大原则就是:
- 单一功能原则告诉我们实现类要功能单一;
- 里式替换原则告诉我们不要破坏继承体系;
- 依赖倒置原则告诉我们要面向抽象编程;
- 接口隔离原则告诉我们设计接口要精简单一;
- 迪米特法则告诉我们要降低耦合。
而开闭原则告诉我们:要对修改关闭,对扩展开放。其实前面的五大原则一直反复强调的,几乎每一个原则都在强调的宗旨就是:解耦,单一,高内聚。
开闭原则解读:
- 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法
- 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类
- 抽象层尽量保持稳定,一旦确定即不允许修改
处理范围分类
23种设计模式分类(https://cloud.tencent.com/developer/article/1665573)
根据处理范围不同,设计模式又可分为类模式和对象模式。
- 类模式处理类与子类的关系,通过处理这些关系来建立继承,在编译时候确定下来,属于静态关系;
- 对象模式处理对象之间的关系,运行时发生变化,属于动态关系。
类模式可以理解为利用继承,对象之间是动态的使用组合的方式。
按照设计原则来说,组合优于继承,所以可以尽可能选择对象模式
创建型模式
创建型比较好理解,他抽象了对象的实例化过程,将系统与实例的创建解耦。实例由专门的工厂来实现,从而使系统针对实例的抽象接口编程,不依赖任何接口的实现,从而让使用者不需要关注对象的创建细节
- 单例模式 (Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型模式 (Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂模式 (FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂模式 (AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者模式 将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
简单工厂模式—类模式
工厂模式—类模式
抽象工厂模式—对象模式
单例模式—对象模式
使用场景:
要求生成唯一序列号的环境;
在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式 (当然,也可以直接声明为static的方式)。
懒汉式
package com.hrs.test.design.singleton;
/**
* 懒汉式
* @author hanrensong
* @date 2022/1/17
*/
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
饿汉式
package com.hrs.test.design.singleton;
/**
* 饿汉式
* @author hanrensong
* @date 2022/1/17
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println("success");
}
}
synchronized加锁
package com.hrs.test.design.singleton;
/**
* synchronized加锁
* 同步的代价,必然会一定程度的使程序的并发度降低
* @author hanrensong
* @date 2022/1/17
*/
public class SyncLazySingleton {
private static SyncLazySingleton instance = null;
public SyncLazySingleton() {}
public static synchronized SyncLazySingleton getInstance() {
if (instance == null) {
return new SyncLazySingleton();
}
return instance;
}
public static void main(String[] args) {
System.out.println("success");
}
}
双重检查锁
package com.hrs.test.design.singleton;
/**
* 双重检查锁
* @author hanrensong
* @date 2022/1/17
*/
public class DoubleCheckedSingleton {
private volatile static DoubleCheckedSingleton instance = null;
private DoubleCheckedSingleton() {}
/**
* 初始化是需要耗费时间的,但是这个对象的地址其实已经存在
* @return
*/
public DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
// 会存在现象:对象还没有被完整的初始化!得到一个没有初始化完全的对象有什么用
return instance;
}
public static void main(String[] args) {
System.out.println("success");
}
}
建造者模式—对象模式
原型模式—对象模式
行为型模式
行为型涉及到算法和对象间职责的分配,它还处理着对象或类之间的通信模式。在程序运行的时候总是流程复杂,需要很好的协调工作,这个时候就是行为型的出场啦。
常见行为型模式:
- 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
模板方法模式—类模式
解释器模式—类模式
策略模式
迭代器模式
委派模式
观察者模式
责任链模式
命令模式
备忘录模式
状态模式
访问者模式
中介者模式
结构型模式
结构型模式主要用于处理类和对象的组合,以获得更大的结构。
这个概念可以区分大多设计模式,但是也有些比较模糊的,例如:代理模式,它既完成了组合 也有职责的分配 但是它依赖被归类于结构型
我们的目的是区分大多数设计模式,所以记住一些比较特别的就可以啦
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
适配器模式—类模式
使用场景:
你有动机修改一个已经投产中的接口时,适配器模式可能是适合你的模式。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?使用适配器模式,这也是我们例子中提到的。示例—充电电源
被适配功能 ``` package com.hrs.test.design.adapter.socket;
/**
- @author hanrensong
- @date 2022/1/19 */
public class Socket { public void chargingWith220V() { System.out.println(“使用插座直接充电(220V)”); } }
适配后功能
package com.hrs.test.design.adapter.chargev5;
/**
- @author hanrensong
- @date 2022/1/19 */
public interface Charge5V {
void charging();
}
package com.hrs.test.design.adapter.chargev5;
/**
- @author hanrensong
- @date 2022/1/19 */
public class MobilePower implements Charge5V {
@Override
public void charging() {
System.out.println("使用充电宝给手机充电(5V)");
}
}
适配器
package com.hrs.test.design.adapter.chagerAdapter;
import com.hrs.test.design.adapter.chargev5.Charge5V; import com.hrs.test.design.adapter.socket.Socket;
/**
- @author hanrensong
- @date 2022/1/19 */
public class ChargerAdapter extends Socket implements Charge5V { @Override public void charging() { chargingWith220V(); System.out.println(“转化为5V”); } }
客户端
package com.hrs.test.design.adapter;
import com.hrs.test.design.adapter.chagerAdapter.ChargerAdapter; import com.hrs.test.design.adapter.chargev5.Charge5V;
/**
- @author hanrensong
- @date 2022/1/19 */
public class Phone {
public void charge(Charge5V c) {
c.charging();
}
public static void main(String[] args) {
Phone phone = new Phone();
// MobilePower mobilePower = new MobilePower(); // phone.charge(mobilePower);
ChargerAdapter chargerAdapter = new ChargerAdapter();
phone.charge(chargerAdapter);
}
}
```