前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式常用的七大原则

设计模式常用的七大原则

作者头像
彼岸舞
发布2022-05-28 09:18:17
2890
发布2022-05-28 09:18:17
举报

之前没有设计模式基本忘完了, 再刷一遍

设计模式的目的

编写软件过程中, 程序员面临着来自 耦合性, 内聚性以及可维护性, 可扩展性, 重用性, 灵活性等多方面的挑战, 设计模式是为了让程序(软件), 具有更好的

  1. 代码重用性(即: 相同功能的代码, 不用多次编写)
  2. 可读性(即: 编程规范性, 便于其他程序员的阅读和理解)
  3. 可扩展性(即: 当需要增加新功能时, 非常的方便, 称为可维护)
  4. 可靠性(即: 当我们增加新的功能后, 对原来的功能没有影响)
  5. 使程序呈现高内聚, 低耦合的特性

金句:

设计模式包含了面向对象的精髓, "懂了设计模式, 你就懂了面向对象分析和设计(OOA/D)的精要"

Scott Mayers 在其巨著<<Effective C++>> 就曾经说过: C++老手和新手的区别就是前者手背上有很多伤疤

设计模式七大原则

设计模式原则, 其实就是程序员在编程时, 应当遵守的原则, 也是各种设计模式的基础(即: 设计模式为什么这样设计的依据)

设计模式常用的七大原则

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒转(倒置)原则
  4. 里式替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则

单一职责原则

基本介绍

对于类来说, 即一个类只负责一项职责, 如类A负责两个不同的职责: 职责1, 职责2, 当职责1需求发生变更而改变A时, 可能造成职责2执行错误, 所以需要将类A的粒度拆解为 A1和A2, 分别负责职责1和职责2,这样需求变更的时候,修改A1, 而不会影响到A2

应用实例

需求

提供交通工具的运行功能

实现

代码语言:javascript
复制
package com.dance.design.principles;

/**
 * 单一职责原则
 */
public class SingleResponsibilityPrinciple {

    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("模特车");
        vehicle.run("汽车");
        vehicle.run("飞机");
    }

}

// 交通工具类
class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle + " 在公路上运行");
    }
}

可以看见, 这个类违反了单一职责原则,他不止维护了陆地上的,还维护了天上飞的,当需求变更的时候,比如飞机开始在公路上跑,后面改成了天上飞,此时就需要修改了,但是修改这个方法还会影响摩托车和汽车的功能,所以不利于维护和职责分离

改进方案一

创建多个类,分开维护

代码语言:javascript
复制
package com.dance.design.principles;

/**
 * 单一职责原则
 */
public class SingleResponsibilityPrinciple2 {

    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("模特车");
        roadVehicle.run("汽车");
        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飞机");
    }

}

// 交通工具类
class RoadVehicle{
    public void run(String vehicle){
        System.out.println(vehicle + " 在公路上运行");
    }
}

// 交通工具类
class AirVehicle{
    public void run(String vehicle){
        System.out.println(vehicle + " 在天上飞");
    }
}

这样虽然遵守了单一职责原则, 但是改动量,比较大,而且不容易看出二者之间的业务关系,对于后续开发来说,还要参考之前的, 不利于维护,并且对代码的入侵会很大,需要具体的了解实现类

改进方案二

与其创建多个类,不如直接扩展方法

代码语言:javascript
复制
package com.dance.design.principles;

/**
 * 单一职责原则
 */
public class SingleResponsibilityPrinciple3 {

    public static void main(String[] args) {
        Vehicle2 vehicle = new Vehicle2();
        vehicle.run("模特车");
        vehicle.run("汽车");
        vehicle.runAir("飞机");
    }

}

// 交通工具类
class Vehicle2{
    public void run(String vehicle){
        System.out.println(vehicle + " 在公路上运行");
    }
    public void runAir(String vehicle){
        System.out.println(vehicle + " 在天上飞");
    }
}

这种方式,没有对类做很大的修改,只是扩展方法,并且可以看出二者的关系,这种做法虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上还是遵守的

改进方案三

虽然老师讲的就到这里了,但是我还是想改一下,就是将Vehicle抽象化成抽象类,然后通过实现去完成不同的功能

代码语言:javascript
复制
package com.dance.design.principles;

/**
 * 单一职责原则
 */
public class SingleResponsibilityPrinciple4 {

    public static void main(String[] args) {
        Vehicle3 vehicle = new RoadVehicle2();
        vehicle.run("模特车");
        vehicle.run("汽车");
        vehicle = new AirVehicle2();
        vehicle.run("飞机");
    }

}

abstract class Vehicle3{
    protected abstract void run(String vehicle);
}

// 交通工具类
class RoadVehicle2 extends Vehicle3{
    @Override
    public void run(String vehicle){
        System.out.println(vehicle + " 在公路上运行");
    }
}

// 交通工具类
class AirVehicle2 extends Vehicle3{
    @Override
    public void run(String vehicle){
        System.out.println(vehicle + " 在天上飞");
    }
}

这样的话, 类的职责完全分开了,并且通过继承可以看出二者之间的关系,在后续扩展的时候,可以通过继承抽象类来扩展, 并且在具体使用的时候完全可以用抽象类, new具体的实现类, 对之前的业务修改也不会很大, 比如后面添加在水里游的,直接继承抽象类即可

注意事项和细节

  1. 降低类的复杂度, 一个类只负责一项职责
  2. 提高类的可读性, 可维护性
  3. 降低变更引起的风险
  4. 通常情况下, 我们应当遵守单一职责原则, 只有逻辑足够简单, 才可以在代码级别违反, 只有类中方法数量足够少,才可以在方法级别遵守

接口隔离原则

基本介绍

客户端不因该依赖它不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上

图解

问题描述:

  1. 类A通过接口Interface1依赖类B, 类C通过Interface1依赖类D, 如果Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法

引入接口隔离原则:

  1. 按照接口隔离原则应该
    1. 将接口Interface1拆解为独立的几个接口(这里我们拆分为3个接口),类A和类C分别与他们需要的接口建立依赖关系,也即是采用接口隔离原则

应用实例

需求

类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D

实现

代码语言:javascript
复制
package com.dance.design.principles.interfaceGL;

/**
 * 接口隔离原则
 */
public class InterfaceIsolationPrinciple {
    public static void main(String[] args) {

    }
}

/**
 * 接口
 */
interface Interface1 {
    void operation1();

    void operation2();

    void operation3();

    void operation4();

    void operation5();
}

class B implements Interface1 {
    @Override
    public void operation1() {
        System.out.println("B 实现了 op1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 op2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 op3");
    }

    @Override
    public void operation4() {
        System.out.println("B 实现了 op4");
    }

    @Override
    public void operation5() {
        System.out.println("B 实现了 op5");
    }
}
class D implements Interface1 {
    @Override
    public void operation1() {
        System.out.println("D 实现了 op1");
    }

    @Override
    public void operation2() {
        System.out.println("D 实现了 op2");
    }

    @Override
    public void operation3() {
        System.out.println("D 实现了 op3");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 op4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 op5");
    }
}

/**
 * A类依赖于 接口的 1 2 3 方法
 */
class A {
    public void dp1(Interface1 i){
        i.operation1();
    }
    public void dp2(Interface1 i){
        i.operation2();
    }
    public void dp3(Interface1 i){
        i.operation3();
    }
}
/**
 * C类依赖于 接口的 1 4 5 方法
 */
class C {
    public void dp1(Interface1 i){
        i.operation1();
    }
    public void dp4(Interface1 i){
        i.operation4();
    }
    public void dp5(Interface1 i){
        i.operation5();
    }
}

可看出这样是存在很多问题的,类A通过Interface1依赖类B, 类C通过接口依赖类D, 如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现它们不需要的方法

改进方案

将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系,也就是采用接口隔离原则

按照接口隔离原则拆分

  1. 类A和类C都需要依赖到op1,所以将op1拆分为一个接口
  2. 类A通过Interface1依赖于类B, 只需要类B提供1,2,3三个方法,而且op1已经拆分为独立的接口了,所以将op2和op3拆分为一个接口
  3. 类C通过Interface1依赖于类D, 只需要类D提供1,4,5三个方法,而且op1已经拆分为独立的接口了,所以将op4和op5拆分为一个接口

我感觉老师的这个模型还是有点复杂,而且有改进的空间,我对此模型进行了优化

通过接口3和4直接继承2,这样就不需要去维护和2之间的关系了, A还是直接对3,B实现3, C直接对4,D实现4

代码语言:javascript
复制
package com.dance.design.principles.interfaceGL;

/**
 * 接口隔离原则
 */
public class InterfaceIsolationPrinciple2 {
    public static void main(String[] args) {
        A1 a1 = new A1();
        B1 b1 = new B1();
        a1.dp1(b1);
        a1.dp2(b1);
        a1.dp3(b1);
        C1 c1 = new C1();
        D1 d1 = new D1();
        c1.dp1(d1);
        c1.dp4(d1);
        c1.dp5(d1);
    }
}

interface Interface2 {
    void operation1();
}

interface Interface3 extends Interface2 {
    void operation2();

    void operation3();
}

interface Interface4 extends Interface2 {
    void operation4();

    void operation5();
}

class B1 implements Interface3 {
    @Override
    public void operation1() {
        System.out.println("B 实现了 op1");
    }

    @Override
    public void operation2() {
        System.out.println("B 实现了 op2");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 op3");
    }
}

class D1 implements Interface4 {
    @Override
    public void operation1() {
        System.out.println("D 实现了 op1");
    }

    @Override
    public void operation4() {
        System.out.println("D 实现了 op4");
    }

    @Override
    public void operation5() {
        System.out.println("D 实现了 op5");
    }
}

/**
 * A类依赖于 接口的 1 2 3 方法
 */
class A1 {
    public void dp1(Interface3 i) {
        i.operation1();
    }

    public void dp2(Interface3 i) {
        i.operation2();
    }

    public void dp3(Interface3 i) {
        i.operation3();
    }
}

/**
 * C类依赖于 接口的 1 4 5 方法
 */
class C1 {
    public void dp1(Interface4 i) {
        i.operation1();
    }

    public void dp4(Interface4 i) {
        i.operation4();
    }

    public void dp5(Interface4 i) {
        i.operation5();
    }
}

依赖倒转(倒置)原则

基本介绍

依赖倒转原则

  1. 高层模块,不应该依赖低层模块,二者都应该依赖其抽象
  2. 抽象不应该依赖于细节, 细节应该依赖抽象
  3. 依赖倒转(倒置)的中心思想是面向接口编程
  4. 依赖倒转原则是基于这样的设计理念: 相对于细节的多变性,抽象的东西要稳定的多, 以抽象为基础搭建的架构比以细节为基础的架构要稳定的多,在Java中,抽象是指接口或抽象类, 细节就是具体的实现类
  5. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

应用实例

需求

完成Person接收消息的功能

实现

代码语言:javascript
复制
package com.dance.design.principles.inversion;

public class DependenceInversionPrinciple {
    public static void main(String[] args) {
        new Person().receive(new Email());
    }
}
class Email {
    public String getInfo(){
        return "电子邮件信息: Hello world";
    }
}
class Person{
    // 消息接收
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

这样基本就实现了, 但是存在一些问题, 后续需求改造,不利于扩展

使用依赖倒转原则改造

原来我们是直接依赖细节的,也就是具体实现,而依赖倒转原则提倡的是依赖于抽象,所以, 我们把消息抽出一个抽象层或者功能接口,让Person去依赖

我个人偏向于抽象成功能接口的,因为接收消息,本身属于功能

让他们通过抽象去管理,而不是通过细节

改进方案

代码语言:javascript
复制
package com.dance.design.principles.inversion;

public class DependenceInversionPrinciple2 {
    public static void main(String[] args) {
        new Person2().receive(new Email2());
        new Person2().receive(new Wechat2());
    }
}

interface Message {
    String getInfo();
}

class Email2 implements Message {
    @Override
    public String getInfo() {
        return "电子邮件信息: Hello world";
    }
}

class Wechat2 implements Message {
    @Override
    public String getInfo() {
        return "微信: Hello world";
    }
}

class Person2 {
    // 消息接收
    public void receive(Message message) {
        System.out.println(message.getInfo());
    }
}

如果后续再次新增需求,扩展了短信或者QQ,都可以通过实现Message类来实现

依赖关系传递的三种方式

接口传递

代码语言:javascript
复制
package com.dance.design.principles.inversion;

/**
 * 通过接口传递依赖
 */
public class DepGx {
    public static void main(String[] args) {
        Tv tv = new Tv();
        XiaoMi xiaoMi = new XiaoMi();
        tv.open(xiaoMi);
        tv.close(xiaoMi);
    }
}

/**
 * 开关接口
 */
interface IOpenAndClose {
    void open(ITV itv);
    void close(ITV itv);
}

/**
 * 电视接口
 */
interface ITV{
    void open();
    void close();
}

/**
 * 小米实现
 */
class XiaoMi implements ITV{
    @Override
    public void open() {
        System.out.println("小米电视 open...");
    }

    @Override
    public void close() {
        System.out.println("小米电视 close...");
    }
}

/**
 * 电视实现开关功能
 */
class Tv implements IOpenAndClose{
    @Override
    public void open(ITV itv) {
        itv.open();
    }

    @Override
    public void close(ITV itv) {
        itv.close();
    }
}

通过接口传递具体的实现

构造方法传递

基于接口传递改造

虽然现在面向的是接口,但是接口的高度却不够, 应为现在是ITV直到电视的高度,我现在想扩展冰箱,就不足了,而且冰箱也有开关的功能,所以进行模型优化

我在电视和冰箱的上层抽离出了家电,然后让开关的接口直接依赖于家电接口,这样就可以通用冰箱和电视了,按道理来说,突然感觉抽象成接口直接让IOpenAndClose去面对家电功能接口又不太好,应该抽象出来一个抽象类(家电)去实现这个接口,然后让小米TV和海尔冰箱去继承这个抽象类的,在下面的Setter方式传递的时候再优化

代码语言:javascript
复制
package com.dance.design.principles.inversion;

/**
 * 通过接口传递依赖
 */
public class DepGx {
    public static void main(String[] args) {
        XiaoMiTv xiaoMiTv = new XiaoMiTv();
        xiaoMiTv.open(new XiaoMi());
        xiaoMiTv.close(new XiaoMi());
        HaiErBx haiErBx = new HaiErBx();
        haiErBx.open(new HaiEr());
        haiErBx.close(new HaiEr());
    }
}

/**
 * 开关接口
 */
interface IOpenAndClose {
    void open(HouseholdElectricalAppliances householdElectricalAppliances);
    void close(HouseholdElectricalAppliances householdElectricalAppliances);
}

/**
 * 家电功能接口
 */
interface HouseholdElectricalAppliances {
    void open();
    void close();
}

/**
 * 电视接口
 */
interface ITV extends HouseholdElectricalAppliances{

}

/**
 * 冰箱接口
 */
interface IRefrigerator extends HouseholdElectricalAppliances{

}

/**
 * 小米遥控器
 */
class XiaoMi implements ITV{
    @Override
    public void open() {
        System.out.println("小米电视 open...");
    }

    @Override
    public void close() {
        System.out.println("小米电视 close...");
    }
}

/**
 * 小米电视
 */
class XiaoMiTv implements IOpenAndClose{
    @Override
    public void open(HouseholdElectricalAppliances itv) {
        itv.open();
    }

    @Override
    public void close(HouseholdElectricalAppliances itv) {
        itv.close();
    }
}

/**
 * 海尔冰箱遥控器
 */
class HaiEr implements IRefrigerator {
    @Override
    public void open() {
        System.out.println("海尔冰箱 手动打开");
    }

    @Override
    public void close() {
        System.out.println("海尔冰箱 手动关闭");
    }
}

/**
 * 海尔冰箱
 */
class HaiErBx implements IOpenAndClose{
    @Override
    public void open(HouseholdElectricalAppliances iRefrigerator) {
        iRefrigerator.open();
    }

    @Override
    public void close(HouseholdElectricalAppliances iRefrigerator) {
        iRefrigerator.close();
    }
}

Setter方式传递

又对上面的模型进行了优化, IOpenAndClose这个开关功能类, 并不应该面向家电抽象,应为他是一个通用的功能, 应该面向更高层的对象, 那就是泛型<T>, 谁实现,谁指定面向什么,应为开关可能是基于电视的,但是还可能是基于门的,没有任何一类可以归纳它,所以定义成泛型, 然后家电抽象类去实现这个接口,提供默认的调用实现, 然后小米电视和海尔冰箱去继承家电的抽象类,应为抽象类中提供了默认的实现,而且业务也就是简单的调用而已,所以不需要重写, 并且家电抽象类指定的泛型是家电功能接口,也就是Hou...able, 具体的打开方式由另外的功能类去实现,也就是小米Able和HaiErAble, 而家电抽象类面向的就是家电功能接口,emm这个模型比上面的要好很多

只是用来做例子而已,如果是真正开发的话,按道理来说, 家电下面应该还可以抽象出电视抽象类,冰箱抽象类,应为都是种类而不是具体的实现,所以... 开发还是要看具体的实践

代码语言:javascript
复制
package com.dance.design.principles.inversion.two;

public class DepGX2 {
    public static void main(String[] args) {
        HouseholdElectricalAppliances xiaomi = new XiaoMiTv();
        HouseholdElectricalAppliancesAble xiaomiable = new XiaoMiAble();
        xiaomi.open(xiaomiable);
        xiaomi.close(xiaomiable);
        HouseholdElectricalAppliances haier = new HaiErBx();
        HouseholdElectricalAppliancesAble haierable = new HaiErAble();
        haier.open(haierable);
        haier.close(haierable);
    }
}
/**
 * 开关接口
 */
interface IOpenAndClose<T> {
    void open(T t);
    void close(T t);
}

/**
 * 家电功能接口
 */
interface HouseholdElectricalAppliancesAble{
    void open();
    void close();
}

/**
 * 家电抽象类
 */
abstract class HouseholdElectricalAppliances implements IOpenAndClose<HouseholdElectricalAppliancesAble> {
    @Override
    public void open(HouseholdElectricalAppliancesAble householdElectricalAppliancesAble) {
        householdElectricalAppliancesAble.open();
    }

    @Override
    public void close(HouseholdElectricalAppliancesAble householdElectricalAppliancesAble) {
        householdElectricalAppliancesAble.close();
    }
}

/**
 * 小米遥控器
 */
class XiaoMiAble implements HouseholdElectricalAppliancesAble{
    @Override
    public void open() {
        System.out.println("小米电视 open...");
    }

    @Override
    public void close() {
        System.out.println("小米电视 close...");
    }
}
/**
 * 冰箱遥控器
 */
class HaiErAble implements HouseholdElectricalAppliancesAble{
    @Override
    public void open() {
        System.out.println("冰箱 open...");
    }

    @Override
    public void close() {
        System.out.println("冰箱 close...");
    }
}
/**
 * 小米电视
 */
class XiaoMiTv extends HouseholdElectricalAppliances{

}
/**
 * 小米电视
 */
class HaiErBx extends HouseholdElectricalAppliances{

}

额,好像忘记改成Setter入参了,好吧,基于这个改造一下

因为模型设计的很好,所以扩展起来也很方便,因为都要改成Setter的直接在家电抽象类中改造就好,不必每个实现类都改

代码语言:javascript
复制
/**
 * 家电抽象类
 */
abstract class HouseholdElectricalAppliances implements IOpenAndClose<HouseholdElectricalAppliancesAble> {

    /**
     * 功能接口
     */
    protected HouseholdElectricalAppliancesAble householdElectricalAppliancesAble;
    
    @Override
    public void open(HouseholdElectricalAppliancesAble householdElectricalAppliancesAble) {
        householdElectricalAppliancesAble.open();
    }

    @Override
    public void close(HouseholdElectricalAppliancesAble householdElectricalAppliancesAble) {
        householdElectricalAppliancesAble.close();
    }
    
    public void setHouseholdElectricalAppliancesAble(HouseholdElectricalAppliancesAble householdElectricalAppliancesAble){
        this.householdElectricalAppliancesAble = householdElectricalAppliancesAble;
    }

    public void open(){
        open(householdElectricalAppliancesAble);
    }
    public void close(){
        close(householdElectricalAppliancesAble);
    }
}

提供一个成员变量,然后提供Set方法,并且重载open和close方法

代码语言:javascript
复制
public class DepGX2 {
    public static void main(String[] args) {
        HouseholdElectricalAppliances xiaomi = new XiaoMiTv();
        xiaomi.setHouseholdElectricalAppliancesAble(new XiaoMiAble());
        xiaomi.open();
        xiaomi.close();
        HouseholdElectricalAppliances haier = new HaiErBx();
        haier.setHouseholdElectricalAppliancesAble(new HaiErAble());
        haier.open();
        haier.close();
    }
}

调用也很方便,okk了

注意事项和细节

  1. 低层模块尽量都要有抽象类或者接口或者两者都有,程序稳定性更好
  2. 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
  3. 继承时遵循里式替换原则

里式替换原则

OO中的继承性的思考和说明

  1. 继承包含这样一层含义: 父类中凡是已经实现好的方法,实际上是在设计规范和契约, 虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏
  2. 继承在给程序设计带来便利的同时,也带来了弊端,比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象之间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有子类,并且父类修改后, 所有涉及到子类的功能都有可能产生故障
  3. 问题提出:
    1. 在编程中, 如何正确使用继承? => 里式替换原则

基本介绍

  1. 里式替换原则,在1988年,由麻省理工学院的一位姓里的女士提出的
  2. 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有任何变化, 那么类型T2是类型T1的子类型,换句话说, 所有引用基类的地方必须能透明地使用其子类对象
  3. 在使用继承时,遵循里式替换原则,在子类中尽量不要重写父类的方法
  4. 里式替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下, 可以通过聚合, 组合, 依赖, 来解决问题

问题和思考

代码语言:javascript
复制
package com.dance.design.principles.internal;

/**
 * 里式替换原则
 */
public class InternalSubstitutionPrinciple {
    public static void main(String[] args) {
        System.out.println(new B().fun1(11, 3));// 本意是求11-3
        System.out.println(new B().fun2(11, 3));// 和 + 9
    }
}

/**
 * 提供减法功能
 */
class A {
    public int fun1(int a, int b) {
        return a - b;
    }
}

/**
 * 扩展自A 实现加法
 */
class B extends A {
    // 无意识重写了A的方法
    @Override
    public int fun1(int a, int b) {
        return a + b;
    }

    public int fun2(int a, int b) {
        return fun1(a, b) + 9;
    }
}

这个案例本意是emm这我如何说呢,基本一看就看出来了,但是如果项目比较复杂, 那就不一定了

解决办法

  1. 我们发现原来运行正常的功能现在出现了错误,因为B无意中重写了Fun1造成了原有功能的错误

通用的做法就是: 原来的父类和子类都继承一个更通俗的基类,原有的继承去掉,采用依赖, 组合, 聚合等关系代替

改进方案

代码语言:javascript
复制
package com.dance.design.principles.internal;

/**
 * 里式替换原则
 */
public class InternalSubstitutionPrinciple {
    public static void main(String[] args) {
        System.out.println(new B().fun1(11, 3));// 11 + 3
        System.out.println(new B().fun2(11, 3));// 和 + 9
        System.out.println(new B().fun3(11, 3));// 11 - 3
    }
}

class Base{

}

/**
 * 提供减法功能
 */
class A extends Base{
    public int fun1(int a, int b) {
        return a - b;
    }
}

/**
 * 扩展自A 实现加法
 */
class B extends Base {

    /**
     * 采用依赖的关系来扩展
     */
    private A a = new A();

    // 无意识重写了A的方法
    public int fun1(int a, int b) {
        return a + b;
    }

    public int fun2(int a, int b) {
        return fun1(a, b) + 9;
    }

    public int fun3(int a,int b){
        return this.a.fun1(a,b);
    }
}

采用依赖代替继承

开闭原则

基本介绍

  1. 开闭原则,是编程中最基础,最重要的设计原则
  2. 一个软件实体, 如类, 实体, 模块和函数,应该对扩展开发(对提供方),对修改关闭(对调用方),用抽象构建框架,用实现扩展细节
  3. 当软件变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修已有的代码,来实现变化
  4. 编程中遵循其他原则, 以及使用设计模式的目的就是遵循开闭原则

代码片段

需求

画图形的功能

类图设计

代码实现

代码语言:javascript
复制
package com.dance.design.principles.ocp;

/**
 * 开闭原则
 */
public class OpenClosePrinciples {
    public static void main(String[] args) {
        Ge ge = new Ge();
        ge.ds(new R());
        ge.ds(new C());
        ge.ds(new T());
    }
}

/**
 * 绘图的类[使用方]
 */
class Ge{
    /**
     * 接收SP对象,根据SP绘制不同的图像
     */
    public void ds(Sp sp){
        if (sp.m_type == 1){
            dr(sp);
        }else if(sp.m_type == 2){
            dc(sp);
        }else if(sp.m_type == 3){
            dt(sp);
        }
    }
    private void dr(Sp sp){
        System.out.println("绘制矩形");
    }
    private void dc(Sp sp){
        System.out.println("绘制圆形");
    }
    private void dt(Sp sp){
        System.out.println("绘制三角形");
    }
}

/**
 * 基类 用于类型判断
 */
class Sp{
    int m_type;
}

/**
 * 矩形
 */
class R extends Sp{
    R(){
        m_type = 1;
    }
}

/**
 * 圆形
 */
class C extends Sp{
    C(){
        m_type = 2;
    }
}

/**
 * 三角形
 */
class T extends Sp{
    T(){
        m_type = 3;
    }
}

改进方案

使用开闭原则改进,这里具体的绘制,显然不应该交给Ge去实现,应为他应该只是提供功能的绘制,而不是细节,并且在扩展的时候还需要去修改

代码语言:javascript
复制
package com.dance.design.principles.ocp;

/**
 * 开闭原则
 */
public class OpenClosePrinciples {
    public static void main(String[] args) {
        Ge ge = new Ge();
        ge.ds(new R());
        ge.ds(new C());
        ge.ds(new T());
    }
}

/**
 * 绘图的类[使用方]
 */
class Ge{
    /**
     * 接收SP对象,根据SP绘制不同的图像
     */
    public void ds(Sp sp){
        sp.ds();
    }
}

/**
 * 基类 用于类型判断
 */
abstract class Sp{
    int m_type;
    abstract void ds();
}

/**
 * 矩形
 */
class R extends Sp{
    R(){
        m_type = 1;
    }
    public void ds(){
        System.out.println("绘制矩形");
    }
}

/**
 * 圆形
 */
class C extends Sp{
    C(){
        m_type = 2;
    }
    public void ds(){
        System.out.println("绘制圆形");
    }
}

/**
 * 三角形
 */
class T extends Sp{
    T(){
        m_type = 3;
    }

    public void ds(){
        System.out.println("绘制三角形");
    }
}

这样的话如果还要扩展其他类型,只需要直接继承Sp类,实现ds方法即可, 不需要对其他的地方进行修改

迪米特法则

基本介绍

  1. 一个对象应该对其他对象保持最少的了解
  2. 类与类关系越密切,耦合度越大
  3. 迪米特法则又称为最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说, 对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息
  4. 迪米特法则还有个更简单的定义,只与直接的朋友通信
    1. 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系,耦合的方式有很多依赖, 关联, 组合, 聚合等, 其中,我们称出现成员变量, 方法参数,方法返回值中的类为直接朋友,而出现在局部变量中的类不是直接的朋友,也就是说,陌生的类最好不要以局部变量的形式出现在类的内部

应用实例

需求

一个学校,下属有各个学院和总部,要求打印出总部员工ID和学院员工ID

实现

代码语言:javascript
复制
package com.dance.design.principles.demeter;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 迪米特法则
 */
public class Demeter1 {
    public static void main(String[] args) {
        ScManage scManage = new ScManage();
        scManage.printAll(new CepManage());
    }
}

/**
 * 学校总部员工
 */
class Emp{
    public int id;

    public Emp(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                '}';
    }
}

/**
 * 学院员工
 */
class Cep{
    public int id;

    public Cep(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Cep{" +
                "id=" + id +
                '}';
    }
}

/**
 * 学院员工管理类
 */
class CepManage{
    /**
     * 返回学院的员工
     */
   public List<Cep> getAllCep(){
       return IntStream.range(0,10).mapToObj(Cep::new).collect(Collectors.toList());
   }
}

/**
 * 学校管理类
 */
class ScManage{
    /**
     * 返回学校总部的员工
     */
    public List<Emp> getAllEmp(){
        return IntStream.range(0,5).mapToObj(Emp::new).collect(Collectors.toList());
    }

    void printAll(CepManage cepManage){
        System.out.println("学院员工--------");
        cepManage.getAllCep().forEach(System.out::println);
        System.out.println("学校员工--------");
        getAllEmp().forEach(System.out::println);
    }
}

设计问题:

  1. ScManage中,Cep并不是ScManage的直接朋友
  2. 按照迪米特法则,应该避免类中出现这样的非直接朋友关系的耦合
  3. 对代码按照迪米特法则改进

改进方案

CepManage中应该自身提供打印输出的方法,将逻辑封装到自身内部,而不是由外部去实现

CepManage中增加方法

代码语言:javascript
复制
public void printAll(){
     getAllCep().forEach(System.out::println);
 }

ScManage中调用即可

代码语言:javascript
复制
void printAll(CepManage cepManage){
      System.out.println("学院员工--------");
      cepManage.printAll();
      System.out.println("学校员工--------");
      getAllEmp().forEach(System.out::println);
  }

注意事项和细节

  1. 迪米特法则的核心是降低类之间的耦合
  2. 但是注意: 由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

合成复用原则

基本介绍

原则是尽量使用合成/聚合的方式, 而不是使用继承

设计原则的核心思想

  1. 找到程序中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  2. 面向抽象编程(接口/抽象类),而不是面向细节编程(实现类)
  3. 为了交互对象之间的松耦合设计而努力
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 设计模式的目的
  • 设计模式七大原则
    • 设计模式常用的七大原则
    • 单一职责原则
      • 基本介绍
        • 应用实例
          • 需求
          • 实现
          • 改进方案一
          • 改进方案二
          • 改进方案三
        • 注意事项和细节
        • 接口隔离原则
          • 基本介绍
            • 应用实例
              • 需求
              • 实现
              • 改进方案
          • 依赖倒转(倒置)原则
            • 基本介绍
              • 应用实例
                • 需求
                • 实现
                • 改进方案
              • 依赖关系传递的三种方式
                • 接口传递
                • 构造方法传递
                • Setter方式传递
              • 注意事项和细节
              • 里式替换原则
                • OO中的继承性的思考和说明
                  • 基本介绍
                    • 问题和思考
                      • 解决办法
                      • 开闭原则
                        • 基本介绍
                          • 代码片段
                            • 需求
                            • 类图设计
                            • 代码实现
                          • 改进方案
                          • 迪米特法则
                            • 基本介绍
                              • 应用实例
                                • 需求
                                • 实现
                                • 改进方案
                              • 注意事项和细节
                              • 合成复用原则
                                • 基本介绍
                                • 设计原则的核心思想
                                相关产品与服务
                                短信
                                腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档