设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性。我们使用设计模式最终的目的是实现代码的 高内聚 和低耦合。
耦合:
也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量
。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差
。
内聚:
故名思议,表示内部间聚集、关联的程度,那么高内聚就是指要高度的聚集和关联。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事
。它描述的是模块内的功能联系。
- 开闭原则
对扩展开放,对修改关闭
。 | - 单一职责原则 即一个类只负责相应领域的职责,即
不要存在多于一个导致类变更的原因
。 - 里氏代换原则 子类型必须能够替换它们的父类型。一个软件实体如果使用的是一个父类,那么当把这个父类替换成继承该父类的子类,程序的行为不会发生任何变化。
- 依赖倒置原则 要依赖于抽象,不要依赖于具体。调用方依赖于抽象耦合。抽象不应当依赖于细节;细节应当依赖于抽象;
要针对接口编程,不针对实现编程。
- 接口隔离原则 客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。
- 最少知识原则 对象与对象之间应该
使用尽可能少的方法来关联
,避免千丝万缕的关系。
一、接口隔离原则(细化接口)
实现步骤:
细化接口,建立单个接口,接口中的方法尽可能少。为各个类专门的接口, 不要试图建立很大的接口供依赖它的类去调用,实现。
单一职责原则原注重的是职责,接口隔离原则注重对接口依赖的隔离。
单一职责原则主要针对的是程序中类中的实现和细节;接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
示例:将一个接口变成三个接口
package principle.jiekougeli;
// 使用多个专门的接口, 而不使用单一的总接口。 接口细化, 方法尽量少。
// 接口仅包含某一类用户定制的方法。
public class T {
}
interface I{
void m1();
void m2();
void m3();
void m4();
void m5();
}
class A{
void d1(I i){
i.m1();
}
void d2(I i) {
i.m2();
}
void d3(I i) {
i.m3();
}
}
class B implements I{
public void m1() {
System.out.println("类B实现接口I的方法1");
}
@Override
public void m2() {
System.out.println("类B实现接口I的方法2");
}
@Override
public void m3() {
System.out.println("类B实现接口I的方法3");
}
public void m4() {}
public void m5() {}
//对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
}
class C{
void d1(I i){
i.m1();
}
void d2(I i) {
i.m2();
}
void d3(I i) {
i.m3();
}
}
class D implements I{
public void m1() {
System.out.println("类D实现接口I的方法1");
}
//对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,
//所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
public void m2() {}
public void m3() {}
public void m4() {
System.out.println("类D实现接口I的方法4");
}
public void m5() {
System.out.println("类D实现接口I的方法5");
}
}
二、开闭原则(原则基础)
步骤
扩展一个类,不去修改一个类。
提高复用性(保护原系统的结构与完整性)、提高可维护性(可读性,易修改)、根据面向对象开发(把所有的事物抽象成对象,然后再针对对象进行操作,但是万物都是在发展变化的,需要在设计之初考虑到尽可能多变化的因素,然后留下接口)。
开闭原则
明确软件实现应该对扩展开放,对修改关闭,其含义是说一个软件应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。
示例 - 书籍扩展
书籍接口
public interface IBook{
public String getName();
public String getPrice();
public String getAuthor();
}
小说类实现书籍接口
public class NovelBook implements IBook{
private String name;
private int price;
private String author;
public NovelBook(String name,int price,String author){
this.name = name;
this.price = price;
this.author = author;
}
public String getAutor(){
return this.author;
}
public String getName(){
return this.name;
}
public int getPrice(){
return this.price;
}
}
客户类
public class Client{
public static void main(Strings[] args){
IBook novel = new NovelBook("笑傲江湖",100,"金庸");
System.out.println(
"书籍名字:"+novel.getName()+
"书籍作者:"+novel.getAuthor()+
"书籍价格:"+novel.getPrice()
);
}
}
添加需要打折销售书籍
1、修改接口
在IBook接口中,增加一个getDiscountPrice()
方法, 所有实现类实现此方法。
缺点:① 书籍接口应该稳定且可靠(接口作为契约和规范)②不是所有的书籍都需要打折
2、修改实现类
直接在getPrice()方法中实现该逻辑,又要需要读取书籍的打折前的价格,getDiscountPrice()有两个方获取价格的方法。
3、通过扩展实现变化【v】
增加一个子类DiscountNodelBook(继承小说类),重写getPrice方法。此方法修改少,对现有的代码没有影响,风险少。
public class OffNovelBook implements NovelBook{
public OffNovelBook(String name,int price,String author){
super(name,price,author);
}
//覆写价格方法,当价格大于40,就打8析,其他价格就打9析
public int getPrice(){
if(this.price > 40){
return this.price * 0.8;
}else{
return this.price * 0.9;
}
}
}
三、迪米拉原则(利用中介)
步骤
最少知道陌生类的各种信息,只需要调用方法就行(可以生成中介,调用中介的方法实现业务逻辑),减少知道陌生类的成员变量和方法。
示例
总公司管理者统计总公司和子公司所有员工信息,调用子公司管理员的方法,而不是直接与公司的员工等也发生联系,这增加了不必要的耦合。
注:直接朋友:类与类耦合关系(以局部变量出现的耦合不属于直接朋友)。陌生的类不要以局部变量的形式出现在类的内部。
总结:
- 使用分公司管理员这个“中介”来与分公司员工发生联系。
- 如果过分使用迪米特原则,会产生大量的中介和传递类。导致系统复杂读提升
- 需要在复杂度,可读性之间权衡
package principle.dimila;
import java.util.ArrayList;
import java.util.List;
/*
* created by sj 2019年8月21日
*/
/**
* 定义:一个类对自己依赖的类知道的越少越好,也就是说,对于依赖的类来说,
* 一个对象, 它的朋友类和类的关系实现里在类间的,而不是方法间。
* 实体类之间发生相互作用 。 降低系统的耦合度
*
* 组合、聚合、依赖。
* 出现在成员变量,方法的输入输出参数中的类
*/
//总公司员工
class Employee{
private int id;
public Employee(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
//子公司员工
class SubEmployee{
private int id;
//传入的参数赋值给当前对象的id
public SubEmployee(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
//子公司管理员
class SubCompanyManager{
/**
* 描述:获取100个分公司的员工信息
*/
List<SubEmployee> getAllSubEmployee() {
List<SubEmployee> list = new ArrayList();
for (int i = 0; i < 100; i++) {
SubEmployee subEmployee = new SubEmployee(i);
list.add(subEmployee);
}
return list;
}
}
//总公司管理者
class CompanyManager{
/**
* 描述:获取30个总公司的员工信息
*/
List<Employee> getAllEmployee() {
List list = new ArrayList();
for (int i = 0; i < 30; i++) {
Employee employee = new Employee(i);
list.add(employee);
}
return list;
}
/**
* 打印总公司和子公司所有的员工
*
* 违背原则:以局部变量出现的耦合不属于直接朋友,所以SubCompanyManager和CompanyManager不属于直接朋友
*/
void printAllEmployee(SubCompanyManager subCompanyManager){
List<SubEmployee> list = subCompanyManager.getAllSubEmployee();
for(SubEmployee subEmployee : list) {
System.out.println(subEmployee.getId());
}
List<Employee> list1 = this.getAllEmployee();
for(Employee employee:list1) {
System.out.println(employee.getId());
}
}
}
class Client{
public static void main(String[] args) {
CompanyManager companyManager = new CompanyManager();
SubCompanyManager subCompanyManager = new SubCompanyManager();
companyManager.printAllEmployee(subCompanyManager);
}
}
/**
* 修改:①为分公司增加了打印分公司员工信息的方法,②总公司直接调用SubManager类的的方法,避免了与分公司的员工发生耦合
*
*/
四、里氏替换原则(父子替换)
步骤:
不要重写父类的非抽象方法(所有引用基类的地方必须能透明底使用其子类的对象),重载时,前置条件(输入参数)更宽松,基础上在参数,后置条件(方法的返回值)比父类更严格范围小。
五、依赖倒置原则(依赖接口)
步骤
通过接口与业务类发生关系。尽量依赖于抽象,具体客户类根据业务细节调用相关的实现类。
示例
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
高层模块(A)依赖接口,底层模块(B,C)实现该接口。高层模块通过接口与这些底层模块发生联系
package principle.yilaidaozhuan;
/*
* created by sj 2019年8月25日
*/
class Book implements IReader {
@Override
public String getContent() {
return "this is a book";
}
}
/**
* mother类负责主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大
*
* 1. 接口传递(底层模块变量都要有抽象类或接口,或者两者皆有)
* 2. 构造方法
* 3. setter方法
*/
class Mother{
void narrate(IReader reader) {
System.out.println(reader.getContent());
}
}
class Client{
public static void main(String[] args) {
Book book = new Book();
Newspaper newspaper = new Newspaper();
Mother mother = new Mother();
mother.narrate(book);
mother.narrate(newspaper);
}
}
/*
1. 接口尽量小,但是要有限度。对接口细化可以提高相许设计灵活性
2. 为依赖接口的类定制服务,暴露给调用的类
3. 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
*/
interface IReader{
public String getContent();
}
class Newspaper implements IReader {
public String getContent(){
return "林书豪17+9助尼克斯击败老鹰……";
}
}
总体来说设计模式分为三大类:
创建型
模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型
模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型
模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
模式类型 | 名称 | 定义 |
---|---|---|
创建型 | 工厂方法模式(Factory Pattern) | 定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法模式是一个类的实例化延迟到子类。 |
创建型 | 抽象工厂模式(Abstract Factory Pattern | 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。 |
创建型 | 单例模式(Singleton Pattern) | 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 |
创建型 | 建造者模式(Builder Pattern) | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 |
创建型 | 原型模式(Prototype Pattern) | 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 |
结构型 | 适配器模式(Adapter Pattern) | 将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作 |
结构型 | 桥接模式(Bridge Pattern) | 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 |
结构型 | 组合模式(Composite Pattern) | 组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。使得用户对单个对象和组合对象的使用具有一致性。 |
结构型 | 装饰器模式(Decorator Pattern) | 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。 |
结构型 | 外观模式(Facade Pattern) | 为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 |
结构型 | 享元模式(Flyweight Pattern) | 运用共享技术有效地支持大量细粒度对象的复用。 |
结构型 | 代理模式(Proxy Pattern) | 为其他对象提供一种代理以控制对这个对象的访问。 |
行为型 | 责任链模式(Chain of Responsibility | Pattern) 使多个对象都有机会处理请求,从而避免请求发送者与接收者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 |
行为型 | 命令模式(Command Pattern) | 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。 |
行为型 | 解释器模式(Interpreter Pattern) | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 |
行为型 | 迭代器模式(Iterator Pattern) | 提供一种方法来访问聚合对象中的各个元素,而不用暴露这个对象的内部表示。 |
行为型 | 中介者模式(Mediator Pattern) | 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 |
行为型 | 备忘录模式(Memento Pattern) | 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。 |
行为型 | 观察者模式(Observer Pattern) | 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。 |
行为型 | 状态模式(State Pattern) | 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 |
行为型 | 策略模式(Strategy Pattern) | 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。 |
行为型 | 模板方法模式(Template Pattern) | 定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 |
行为型模式 | 访问者模式(Visitor Pattern) | 表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 |