文章目录

  • 前言
  • 1. 软件设计模式原则
  • 2. 开闭原则
    • 1. 概念
    • 2. 例子
  • 3. 里氏替换原则
    • 1. 概念
    • 2. 例子
  • 4. 依赖倒置原则
    • 1. 概念
    • 2. 代码
  • 5. 单一职责原则
    • 说明
  • 6. 接口隔离原则
    • 1. 概念
    • 2. 代码
  • 7. 迪米特法则
    • 1. 概念
    • 2. 代码
  • 8. 合成复用原则
    • 1. 概念
    • 2. 代码

前言

文章参考黑马的设计模式讲义以及c语言中文网教程C语言中文网教程,还有一些自己的理解,对于一些概念的东西还是很难自己总结一套出来的。黑马视频: 黑马设计模式,写笔记也是写文章的一种了,光是看给的资料还是不够的,最起码得自己理解敲一遍才行


提示:以下是本篇文章正文内容,下面案例可供参考

1. 软件设计模式原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据七条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。

其实做过项目的朋友应该都能理解,特别是维护一些代码的时候,除了没有注释使比较头疼的,还有一点就是一个类几百甚至上千行这种,有时候比如一个缓存类里面的方法就一大堆,其实这种情况在使用了设计模式情况下可以使得代码变得更加灵活,就比如用一个Cache接口,然后使用一些其他的具体的实现类来实现这个接口,重写方法,需要扩展的时候就直接增加实现类或者增加接口方法就可以了,最后甚至可以用建造者模式把所有的处理器连接起来,其实这就是 Mybatis 源码中对于 Cache 的处理方式。下面就来看看这七大设计模式吧,有些可能不这么准确,有错误欢迎指出

2. 开闭原则

1. 概念

概念:软件实体对扩展开放,对修改关闭

理解:其实总的来说就是要对一个程序进行扩展的时候,不要去修改原来的代码,实现一个热插拔效果。 实际上这样设计也是为了程序容易修改和扩展。

好处:首先就是可复用性高,符合了开闭原则后,如果想要实现类的扩展,只需要在原来的基础上继承抽象类或者实现接口就可以了,不需要再修改源代码。其次就是可扩展性高,如果想要扩展出新的功能,只需要在原来的基础上实现类或者继承接口,再实现方法就可以了。最后就是可读性高,比较如果一个类里面包括了所有的功能,代码越来越多,读起来肯定也越来越麻烦。其实好处应该不止这三个,但是这三个是最基本的。

应用:在使用一些 interceptor 或者 listener 的时候往往需要继承对应的拦截器接口和监听器接口,这时候只需要在我们原来代码的基础上扩展我们自己的拦截器或者监听器就可以了。

2. 例子

开闭原则 - before

这里看一个例子,就以银行业务为例,我们假设银行有取钱和存钱两种业务,下面使用不是接口的写法,代码就都放在一起了:

//银行类
public class Bank {public void save(String name){//存钱System.out.println("用户" + name + "进行了存钱");}public void get(String name){//存钱System.out.println("用户" + name + "进行了取钱");}
}//人物
@Data
@AllArgsConstructor
public class Person{private String name;
}//测试类
public class TestOne {public static void main(String[] args) {Person person = new Person("张三");Bank bank = new Bank();//用户去存钱bank.save(person.getName());//用户去取钱bank.get(person.getName());//用户张三进行了存钱//用户张三进行了取钱}
}
开闭原则 - after

但其实只要分析分析就会发现这样的写法其实并不好,因为银行的业务是很多的,不可能只有这两种,一开始少的可以这么写,但是随着业务的不断增多,类里面的方法也越来越多,耦合度高,类就变得很臃肿,要扩展并不简单。我们优化的代码可以使用接口:

//Bank接口
public interface Bank {//操作public void operation(String name);
}//取钱类
public class BankGet implements Bank{@Overridepublic void operation(String name) {System.out.println("用户" + name + "进行了取钱");}
}//存钱类
public class BankSave implements Bank{@Overridepublic void operation(String name) {System.out.println("用户" + name + "进行了存钱");}
}//测试类
public class TestOne {public static void main(String[] args) {Person person = new Person("张三");new BankSave().operation(person.getName());new BankGet().operation(person.getName());//用户张三进行了存钱//用户张三进行了取钱}
}

这样的写法好处就是容易扩展,比如需要扩展一种转账业务的时候,只需要继承原来的接口,再添加一个转账类就可以了。如果按照原来的写法则需要在银行类里面写方法,耦合度很高。

3. 里氏替换原则

1. 概念

里氏替换原则是面向对象设计的基本原则之一。

概念:派生类(子类)对象可以在程式中代替其基类(超类)对象。

理解:里氏代换原则主要介绍了继承的一些原则,说明了基类和子类之间继承的关系,里面的一些规则说明了什么时候可以继承,什么时候又不可以继承,以及子类对于基类的方法的使用。

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

    • 父类的非抽象方法往往都是遵循一定规范的,既然不是抽象的就不是用来给子类重写的
  • 子类中可以增加自己特有的方法

    • 子类可以有自己的扩展的方法,不需要完全按照父类的方法来写
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松

    • 这句话的意思是当子类定义了一个和父类同名不同参的方法的时候,子类方法的输入参数要比父类的更宽松。比如我们来看下面这张图:

      里氏替换的最基本概念就是子类可以在程序中替换父类,那么假设我们现在有一个方法,父类的参数是手机,而子类重载的方法则是要输入华为手机,那么当我们把父类替换成子类之后,如果参数输入了小米手机,那么子类方法就运行不了,因为按照父类的本意就是参数可以输入任何的手机类型。
  • 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

    • 可以这么理解,如果父类方法的返回值是一个Map,那么子类可以在重载或者重写的方法中返回 Map 或者是 Map 的子类。可以这么理解,子类是实现了抽象父类所有的抽象方法,而且可能也扩展了自己的一些方法,那么这时候使用子类作为返回值是同样可以保证系统运行的。

作用:里氏替换原则确保了重写父类方法之后代码可用性变差的后果,防止子类重写父类之后给系统带来了错误,而且里氏替换原则对子类的定义也确保了系统很好的兼容性,更容易扩展。

对于里氏替换原则,类特别多的程序并且继承关系复杂的程序里面来说是很有效的,保证了一个规范。如果没有遵循,当程序发生错误的时候,就得重新设计类之间的方法了。下面看一个经典的例子:

2. 例子

正方形不是长方形 - before

在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形。

长方形:

public class Rectangle {private double length;private double width;public double getLength() {return length;}public void setLength(double length) {this.length = length;}public double getWidth() {return width;}public void setWidth(double width) {this.width = width;}
}

正方形:重写了 Rectangle 类的长宽方法,并且都设置为相同的数据

public class Square extends Rectangle{@Overridepublic void setWidth(double width) {super.setLength(width);super.setWidth(width);}@Overridepublic void setLength(double length) {super.setLength(length);super.setWidth(length);}
}

现在有一个 RectangleDemo 类,里面的一个 resize 方法依赖于 Rectangle,这个方法的效果就是当宽小于长的时候,宽+1,直到宽大于长

public class RectangleDemo {public static void resize(Rectangle rectangle) {while (rectangle.getWidth() <= rectangle.getLength()) {rectangle.setWidth(rectangle.getWidth() + 1);}}//打印长方形的长和宽public static void printLengthAndWidth(Rectangle rectangle) {System.out.println(rectangle.getLength());System.out.println(rectangle.getWidth());}public static void main(String[] args) {Rectangle rectangle = new Rectangle();rectangle.setLength(20);rectangle.setWidth(10);resize(rectangle);printLengthAndWidth(rectangle);}
}

现在我们先在那个方法中放入一个长方形,结果没有问题

现在我们传入正方形,然后就发现结果无法输出,回到代码中查看原因,其实不难发现,当宽在增加的时候,长也不断增加,所以一直无法返回,直到发生溢出错误:

从上面我们可以得到一个结论:正方形子类不能替换长方形基类作为参数传入这个方法中,由此可以发现,这里的类的继承设计违反了里氏替换原则,正方形不是长方形。那么如何改进? 我们需要重新设计它们的关系,可以设计一个四边形接口,然后让正方形和长方形都继承这个接口,最终的 resize 方法只对长方形有效

正方形不是长方形 - after

正方形:

public class Square implements Quadrilateral{private double side;public double getSide() {return side;}public void setSide(double side) {this.side = side;}@Overridepublic double getLength() {return side;}@Overridepublic double getWidth() {return side;}
}

长方形:

public class Rectangle implements Quadrilateral{private double length;private double width;public void setLength(double length) {this.length = length;}public void setWidth(double width) {this.width = width;}@Overridepublic double getLength() {return length;}@Overridepublic double getWidth() {return width;}
}

四边形接口:

public interface Quadrilateral {//获取长double getLength();//获取宽double getWidth();
}

Demo类,里面的 resize 只对长方形有效,下面的代码传入正方形就会报错,所以就避免了这个问题

public class RectangleDemo {//拓宽public static void resize(Rectangle rectangle) {while (rectangle.getWidth() <= rectangle.getLength()) {rectangle.setWidth(rectangle.getWidth() + 1);}}//打印长方形的长和宽public static void printLengthAndWidth(Quadrilateral rectangle) {System.out.println(rectangle.getLength());System.out.println(rectangle.getWidth());}public static void main(String[] args) {//创建长方形对象Rectangle r = new Rectangle();r.setLength(20);r.setWidth(10);//调用方法进行拓宽resize(r);printLengthAndWidth(r);//20.0//21.0}
}

这样来看,数学上的 正方形是长方形可能看起来是一种属于关系而不是 是 的关系了,正方形是长方形的一个特殊的例子,用正方形来代替长方形是不恰当的。

4. 依赖倒置原则

1. 概念

定义: 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。核心思想就是面向接口编程,而不是面向实现编程。

依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。

  • 在软件设计中,如果抽象设计出来的程序比通过细节设计出来的程序要稳定,以接口或者抽象类为底层搭建出来的程序架构也比较容易扩展
  • 细节交给具体的子类去实现,接口/抽象类负责制定规范

好处:

  • 降低耦合度
  • 提高系统的稳定性
  • 减少并行开发引起的风险
  • 提高代码的可读性和可维护性

2. 代码

依赖倒置 - before

下面来定义一个咖啡店的例子:

美式咖啡:

//美式咖啡
public class AmericanCoffee {private int price;public AmericanCoffee(int price) {this.price = price;}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}public void sell(){System.out.println("卖了一杯美式咖啡,价格是:" + price);}
}

拿铁咖啡:

//拿铁咖啡
public class LatteCoffee {private int price;public LatteCoffee(int price) {this.price = price;}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}public void sell(){System.out.println("卖了一杯拿铁咖啡,价格是:" + price);}
}

咖啡店:

public class CoffeeShop {public static void sell(LatteCoffee latteCoffee){latteCoffee.sell();}
}

客户点一杯咖啡:

public class Client {public static void main(String[] args) {CoffeeShop.sell(new LatteCoffee(1000));//卖了一杯拿铁咖啡,价格是:1000}
}

上面的输出结果没有问题,但是如果我们不想要拿铁咖啡了,想要美式咖啡,那么对于咖啡店类的代码又要进行修改,把传入的参数改为美式咖啡,这样就违背了开闭原则。所以我们做下面的修改,将美式咖啡和拿铁咖啡都继承咖啡接口

依赖倒置 - after

咖啡类实现 Coffee 接口,重写 sell 方法

//咖啡接口
public interface Coffee {//卖咖啡,子类实现public void sell();
}
//拿铁咖啡
public class LatteCoffee implements Coffee{private int price;public LatteCoffee(int price) {this.price = price;}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}@Overridepublic void sell(){System.out.println("卖了一杯拿铁咖啡,价格是:" + price);}
}
//美式咖啡
public class AmericanCoffee implements Coffee{private int price;public AmericanCoffee(int price) {this.price = price;}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}@Overridepublic void sell(){System.out.println("卖了一杯美式咖啡,价格是:" + price);}
}

商店类传入 Coffee 作为参数:

public class CoffeeShop {public static void sell(Coffee coffee){coffee.sell();}
}

客户就可以想要什么咖啡就传入什么类型的对象:

public class Client {public static void main(String[] args) {CoffeeShop.sell(new LatteCoffee(1000));//卖了一杯拿铁咖啡,价格是:1000CoffeeShop.sell(new AmericanCoffee(1000));//卖了一杯美式咖啡,价格是:1000}
}

可以看到,客户端想要什么咖啡就传入什么对象就可以了,如果咖啡店还要扩展一种其他类型的咖啡,只需要创建一个其他咖啡对象实现 Coffee 接口,重写 sell 方法就可以了。

5. 单一职责原则

说明

定义: 单一职责原则又称单一功能原则,规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。

单一职责原则规定了一个类专注于一种职责,比如可以定义不同处理器处理不同请求,对于 GET 请求的处理器就不要去关心 POST 请求是怎么处理的。又比如买水果的店专注于买水果,买汉堡的店专注于卖汉堡。如果让一个类处理多种请求,那么就容易有下面两个缺点

  • 一个类承担的职责太多就会导致其他类的职责减低
  • 当客户需要修改一个类的时候就不得不考虑到也要修改里面原本不属于这个类应该要考虑的职责,比如上面的如果一个 GET 类既处理 GET 请求,也处理 POST 请求,那么当修改这个类的时候就得对不属于职责范围的 POST 请求的方法也得修改

优点: 单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性

  • 可读性高,一个类只处理一种职责,那么程序员在理解这个类的时候只需要专注于这种类型功能的理解就行
  • 可维护性高,比如如果出现 bug,只需要找到 bug 是什么类型的请求,然后到对应的处理类里面去修改就可以了
  • 降低了复杂度,一个类关心一种类型的职责比关心多种类型的职责要更容易处理
  • 当代码变更的时候,实现单一职责原则的类的变动对其他的类的影响更小

案例: 其实对于单一职责的案例有很多,除了上面说的,还有 Netty 中的处理器链,每个处理器对客户端传送过来的数据进行各自的处理。

同样的,对于类的单一职责原则也适合用到方法上,一个方法专注于处理一种问题。但是要注意的是,实现单一职责原则是非常难的,如果写代码之前没有构思好什么类什么方法处理什么职责,那么就容易出现一个类或者一个类处理多种类型的职责的情况,所以对于开发人员的要求是很高的。

6. 接口隔离原则

1. 概念

接口隔离原则(Interface Segregation Principle,ISP)要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。

定义:一个类对另一个类的依赖应该建立在最小的接口上,要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用

优点:

  • 将臃肿的接口分为了更小粒度的接口,提高系统灵活度。比如原来有一个发快递的接口,细分成打包,发货,快递公司接收等等一些列的接口分别处理不同流程
  • 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
  • 定义合理大小的接口有利于系统的稳定性,接口太大或者接口过小都不行。以上面的快递为例。如果不细分为打包,发货,快递公司接收等等接口,那么一个快递接口要处理的职责太多了,我们假设有顺丰和圆通两家公司,就算圆通不提供打包服务,圆通公司实现这一个接口的时候也不得不去处理打包方法。而如果把接口细化,那么圆通公司类就可以不实现打包的接口。此外,接口粒度太小了就导致要实现的方法太多了,增加了系统的复杂度,读起来和修改起来都不方便。
  • 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
  • 可以减少代码冗余,还是上面提到的快递接口,圆通公司不提供打包服务,就不实现打包接口即可。

2. 代码

接口隔离原则 - before

还是以上面的快递公司为例:

//快递接口
public interface Express {//打包服务public void packageEx();//送货服务public void delivery();//售后服务public void afterSales();
}//顺丰公司
public class SFExpress implements Express{@Overridepublic void packageEx() {System.out.println("顺丰进行打包服务");}@Overridepublic void delivery() {System.out.println("顺丰进行送货服务");}@Overridepublic void afterSales() {System.out.println("顺丰进行售后服务");}
}//圆通快递
public class YTOExpress implements Express{@Overridepublic void packageEx() {System.out.println("圆通不提供发货服务");}@Overridepublic void delivery() {System.out.println("圆通进行送货服务");}@Overridepublic void afterSales() {System.out.println("圆通进行售后服务");}
}public class Client {public static void main(String[] args) {//顺丰SFExpress sfExpress = new SFExpress();sfExpress.packageEx();sfExpress.delivery();sfExpress.afterSales();//顺丰进行打包服务//顺丰进行送货服务//顺丰进行售后服务//圆通YTOExpress ytoExpress = new YTOExpress();ytoExpress.packageEx();ytoExpress.delivery();ytoExpress.afterSales();//圆通不提供发货服务//圆通进行送货服务//圆通进行售后服务}
}

输出结果:

可以看到上面的圆通公司打印了不提供发货服务这句话,但是我们想要的效果应该是没有这个方法,而不是必须要实现这个方法,所以为了实现这个效果,再将这个接口进一步细化为三个接口:打包 - 发货 - 售后

接口隔离原则 - after
//售后接口
public interface AfterSelf {//售后服务public void afterSales();
}//送货接口
public interface Delivery {//送货服务public void delivery();
}//打包接口
public interface Package {//打包服务public void packageEx();
}//顺丰公司
public class SFExpress implements AfterSelf, Delivery, Package {@Overridepublic void packageEx() {System.out.println("顺丰进行打包服务");}@Overridepublic void delivery() {System.out.println("顺丰进行送货服务");}@Overridepublic void afterSales() {System.out.println("顺丰进行售后服务");}
}//圆通快递
public class YTOExpress implements AfterSelf, Delivery {@Overridepublic void delivery() {System.out.println("圆通进行送货服务");}@Overridepublic void afterSales() {System.out.println("圆通进行售后服务");}
}public class Client {public static void main(String[] args) {//顺丰SFExpress sfExpress = new SFExpress();sfExpress.packageEx();sfExpress.delivery();sfExpress.afterSales();//顺丰进行打包服务//顺丰进行送货服务//顺丰进行售后服务//圆通YTOExpress ytoExpress = new YTOExpress();ytoExpress.delivery();ytoExpress.afterSales();//圆通进行送货服务//圆通进行售后服务}
}

输出结果:

可以看到,细化接口之后圆通就不需要实现 Package 接口了。

7. 迪米特法则

1. 概念

定义: 迪米特法则又叫最少知识原则,只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。

理解:其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

优点:

  • 简单了耦合度,禁止类之间随便调用
  • 提高系统代码的可复用性以及提高了扩展性
  • 值得注意的是,使用迪米特法则的时候使用恰当会使得系统结构清晰,但是过度使用产生很多的 " 朋友 " 会导致系统的复杂度提高,要修改起来并不简单。所以使用迪米特法则的时候要先设计好什么地方该用,什么地方不该用,不能为了解耦的效果就不断使用中介。

2. 代码

迪米特法则 - 明星经纪人

下面以明星和经纪人为例,明星负责处理业务,而经纪人则负责帮明星安排和粉丝见面以及去和经济公司谈业务:

//粉丝
public class Fans {private String name;public Fans(String name) {this.name=name;}public String getName() {return name;}
}//明星
public class Star {private String name;public Star(String name) {this.name=name;}public String getName() {return name;}
}//经济公司
public class Company {private String name;public Company(String name) {this.name=name;}public String getName() {return name;}
}//经纪人
public class Agent {private Star star;private Fans fans;private Company company;public void setStar(Star star) {this.star = star;}public void setFans(Fans fans) {this.fans = fans;}public void setCompany(Company company) {this.company = company;}public void meeting() {System.out.println(fans.getName() + "与明星" + star.getName() + "见面了。");}public void business() {System.out.println(company.getName() + "与明星" + star.getName() + "洽淡业务。");}
}public class Client {public static void main(String[] args) {Agent agent = new Agent();agent.setStar(new Star("小明"));agent.setCompany(new Company("经济公司"));agent.setFans(new Fans("小红"));//经纪人帮明星管理日程活动agent.business();agent.meeting();//经济公司与明星小明洽淡业务。//小红与明星小明见面了。}
}

8. 合成复用原则

1. 概念

定义: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

理解: 通常类的复用分为继承复用和合成复用两种。继承复用虽然有简单和易实现的优点,但它也存在以下缺点:

  • 继承复用破坏了类的封装性,父类的细节会暴漏给子类,父类对子类是透明的,这种复用叫 “白箱” 复用。
  • 子类与父类的耦合度高。父类中的方法如果发生改变,子类都要去处理父类中改变的方法,不利于类的维护和扩展。
  • 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:

  1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。对于一个对象来说,只能依靠对象内部使用 get 或 set 等方法主动暴露修改的方法给引用的变量,而没有暴露的变量和方法是不会被使用。
  2. 对象间的耦合度低。可以在类的成员位置声明抽象。新的对象通过直接 new 出来成员对象,然后才可以使用内部的方法。
  3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

2. 代码

合成复用原则 - before

汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。类图如下,从这张图中也不难发现,使用继承类尝试了很多的类,首先汽车分为了汽油汽车和电动汽车,而两种汽车又分为了不同颜色的汽车

//汽车抽象类
public abstract class Car {public abstract void move();
}//电动汽车类
public class ElectricCar extends Car{@Overridepublic void move() {System.out.println("电动汽车在跑");}
}//汽油汽车类
public class GasolineCar extends Car{@Overridepublic void move() {System.out.println("汽油汽车在跑");}
}//黑色电动汽车
public class BlackElectricCar extends ElectricCar{@Overridepublic void move() {System.out.println("黑色电动汽车在跑");}
}//红色电动汽车
public class RedElectricCar extends ElectricCar{@Overridepublic void move() {System.out.println("红色电动汽车在跑");}
}//白色电动汽车
public class WhiteElectricCar extends ElectricCar{@Overridepublic void move() {System.out.println("白色电动汽车在跑");}
}//黑色汽油汽车
public class BlackGasolineCar extends GasolineCar{@Overridepublic void move() {System.out.println("黑色汽油汽车在跑");}
}//红色汽油汽车
public class RedGasolineCar extends GasolineCar{@Overridepublic void move() {System.out.println("红色汽油汽车在跑");}
}//白色汽油汽车
public class WhiteGasolineCar extends GasolineCar{@Overridepublic void move() {System.out.println("白色汽油汽车在跑");}
}

结果输出:

合成复用原则 - after

从上面代码我们可以看到使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就再需要定义一个新的汽车类以及不同的颜色子类,但是这样依赖类太多了,所以我们考虑使用合成复用原则,把颜色作为一个属性传入车的对象里面进行调用:

//抽象颜色类
public abstract class Color {public String name;
}//黑色
public class Black extends Color{public Black() {super.name = "黑色";}
}//红色
public class Red extends Color{public Red() {super.name = "红色";}
}//白色
public class White extends Color{public White() {super.name = "白色";}
}//汽车抽象类
public abstract class Car {public abstract void move();
}//电动汽车类
public class ElectricCar extends Car {Color color;public ElectricCar(Color color) {this.color = color;}@Overridepublic void move() {System.out.println(color.name + "电动汽车在跑");}
}//汽油汽车类
public class GasolineCar extends Car {Color color;public GasolineCar(Color color) {this.color = color;}@Overridepublic void move() {System.out.println(color.name + "汽油汽车在跑");}
}public class Client {public static void main(String[] args) {ElectricCar redECar = new ElectricCar(new Red());ElectricCar whiteECar = new ElectricCar(new White());ElectricCar blackECar = new ElectricCar(new Black());redECar.move();whiteECar.move();blackECar.move();//红色电动汽车在跑//白色电动汽车在跑//黑色电动汽车在跑GasolineCar redGaCar = new GasolineCar(new Red());GasolineCar whiteGaCar = new GasolineCar(new White());GasolineCar blackGaCar = new GasolineCar(new Black());redGaCar.move();whiteGaCar.move();blackGaCar.move();//红色汽油汽车在跑//白色汽油汽车在跑//黑色汽油汽车在跑}
}

结果输出:

可以看到经过合成复用之后,如果想要再加入一种汽车就加一个类,再加入一种颜色,就加入 Color 的一个子类就行了,不用再添加具体汽车的具体颜色类。

如有错误,欢迎指出!!!

设计模式 - 软件设计的七大原则相关推荐

  1. 软件设计的七大原则 --开闭原则 里氏替换原则 依赖倒置原则

    在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 7 条原则来开发程序,从而提高软件开发效率.节约软件开发成本和维护成本. 这 7 种设计原则是软件设计 ...

  2. 浅谈软件设计的七大原则

    软件设计原则这个话题看上去很大,乍一看确实不小,但是如果仔细去分析的话可以发现这些原则其实就是为了避免一些问题而提出的一些建议,这些建议呢普遍使用于软件的各个领域,所以给这些建议提高了一个档次就叫做原 ...

  3. 设计模式(面向对象)设计的七大原则

    声明:本人设计模式模块是集合网上资料和老师课件总结的知识点,如本博客有侵权,本人即刻删. 设计模式(面向对象设计)原则,分别是: 1.开放封闭原则:对扩展开放,对修改关闭 2.单一职责原则:一个类只做 ...

  4. 【设计模式系列24】GoF23种设计模式总结及软件设计7大原则

    设计模式总结及软件设计七大原则 设计模式系列总览 前言 软件设计7大原则 开闭原则(Open-Closed Principle,OCP) 里氏替换原则(Liskov Substitution Prin ...

  5. 【设计模式】软件设计遵循——六大原则

    软件设计遵循--六大原则 1)开闭原则 定义:一个软件实体如类.模块和函数应该对扩展开放,对修改关闭 原则:用抽象构建框架,用实现扩展细节 优点:提高软件系统的可复用性和可维护性 2)里氏替换原则 定 ...

  6. 深入理解面向对象设计的七大原则

    一.面向对象设计的七大原则是什么? 1.开放封闭原则 2.里氏转换原则 3.依赖倒转原则 4.组合/聚合原则 5.接口隔离原则 6."迪米特"法则 7.单一职责原则 二.七大原则是 ...

  7. 软件架构设计_架构师内功心法,软件架构设计的七大原则精选案例

    一.软件架构设计的七大原则简介 1.1 开闭原则(Open-Closed Principle,OCP) 开闭原则是一个软件实体如类.模块和函数应该对扩展开放,对修改关闭.所谓的开闭也是对扩展和修改两个 ...

  8. 面向对象设计的七大原则 (包括SOLID原则)

    文章目录 概述 1. 单一原则 2. 里氏替换原则 3. 依赖倒转原则 4. 接口分隔原则(Interface Segregation Principle ,ISP) 5. 迪米特法则 (Law of ...

  9. 视频教程-实用通俗易懂的设计模式-软件设计

    实用通俗易懂的设计模式 15年一线项目从业经验,长期从事大型商业项目管理. 长期主导研发金融,水利行业等,大型商业项目.深入研究项目全生命周期,参与公司产品线定位,架构设计,管理协调实施项目投标方案编 ...

最新文章

  1. Spring Security 之集群Session配置
  2. notepad 没有plugin manager_如何在没有反光镜的情况下在户外拍摄人物?
  3. Mac 下anaconda安装mysqldb的方法
  4. 视频通信关键技术探索及实践
  5. Phonegap win7下环境配置流程
  6. 数据库性能(一):数据库索引原理解析
  7. h5微信本地调试 vue_vueh5中使用微信sdk
  8. android开发 视图联动_android开发_ViewGroup(组视图)-- 五大布局
  9. gdb调试中出现optimized out
  10. 关于co-NP的理解
  11. hive中实现行转列_##[函数]Hive中行列转换(行转列)
  12. mfc控件设置颜色(超全)
  13. ntfs格式的移动硬盘如何在mac电脑写入?
  14. 【毕业设计】大数据招聘数据分析可视化 - python
  15. 一个程序员未来5年的规划
  16. You earned your Program Management Professional (PgMP)® Credential
  17. 电脑时间倒流,程序拒绝穿越:应用程序发生异常 未知的软件异常(0xe06d7363)
  18. 3D电影、游戏里的角色是怎么制作的?
  19. Android S关闭定位开关后,定位权限被AppOps限制。
  20. 18.9.23 PION模拟赛

热门文章

  1. 关于hibernate中invers跟cascade的一点看法
  2. 从零到一:如何用你的电脑成功登录QQ
  3. oracle增加表空间大小
  4. iPhone微信语音导出
  5. softlayer iso_在SoftLayer服务器上使用图形界面
  6. 具有手摇柴油机带动的油泵和空压机带动的柴油喷嘴的涡轮喷气式飞机
  7. 【杭州SEO优化】网站建设细节分析!
  8. 开发环境 EAS登录 license 许可修改
  9. windows 命令行ssh + Xming打开虚拟机的图形界面应用
  10. Web3专属的流支付协议,Zebec把它玩出了新的花样