设计模式-七大设计原则

七大设计原则

在软件开发中,为了提高软件系统的可维护性、复用性、可扩展性、可靠性、灵活性,让程序呈现出高内聚、低耦合。程序员需要尽量根据七条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本

参考链接:

黑马Java设计模式详解

尚硅谷Java设计模式

设计模式系列之设计原则(5)迪米特法则

一、单一职责原则

其核心思想为:一个类,最好只做一件事,只有一个引起它的变化

通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。

单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,
将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度

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

    //交通工具类
    // 方式一:此时Vehicle中的run方法既管公路上跑的,又管天上飞的,违反了单一原则
    // 解决方案:根据交通工具运行方法不同,分解成不同类
    static class Vehicle{
        //方式一:
        public void run(String vehicle){
            System.out.println(vehicle + " 在公路上跑");
        }
    }
}
public class SingleResponsibility_2 {
    public static void main(String[] args) {
        new RoadVehicle().run("汽车");
        new AirVehicle().run("飞机");
    }
    
    /**
     * 方式二:
     * 1. 遵守单一职责原则
     * 2. 但是改动太大,又要分解类,又要修改客户端
     * 改进方案:修改Vehicle类,改动的代码会比较少
     */
    static class RoadVehicle{
        public void run(String vehicle){
            System.out.println(vehicle + " 在公路上跑");
        }
    }

    static class AirVehicle{
        public void run(String vehicle){
            System.out.println(vehicle + " 在天上飞");
        }
    }

    static class WaterVehicle{
        public void run(String vehicle){
            System.out.println(vehicle + " 在水里游");
        }
    }
}

如果严格遵守单一职责原则,那么可以造成类急速增多,维护性反而下降

所以当类中逻辑简单,类中方法足够少,我们可以在方法级别保持单一职责原则

public class SingleResponsibility_3 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.runRoad("汽车");
        vehicle.runWater("船");
        vehicle.runAir("飞机");
    }

    /**
     * 方式三:
     * 1. 这种修改方法没有对原来的类做大的修改,只是增加方法
     * 2. 这里虽然没有在类级别遵守单一职责,但是在方法级别上遵守单一职责
     */
    static class Vehicle{
        public void runRoad(String vehicle){
            System.out.println(vehicle + " 在公路上跑");
        }

        public void runAir(String vehicle){
            System.out.println(vehicle + " 在天上跑");
        }

        public void runWater(String vehicle){
            System.out.println(vehicle + " 在水里跑");
        }
    }
}

二、开闭原则

软件实体应该是可扩展的,并且不可修改的,对扩展开放,对修改关闭

实现:借助于抽象类和接口

public abstract class AbstractSkin {
    abstract public void display();
}
//AbstractSkin的两个子类
public class DefaultSkin extends AbstractSkin{
    @Override
    public void display() {
        System.out.println("默认");
    }
}

public class HeimaSkin extends AbstractSkin{
    @Override
    public void display() {
        System.out.println("黑马");
    }
}
public class SougouInput {
    private AbstractSkin skin;

    public void setSkin(AbstractSkin skin) {
        this.skin = skin;
    }

    public void display(){
        skin.display();
    }

    public static void main(String[] args) {
        SougouInput input = new SougouInput();
		
        //如下例所示,搜狗皮肤的设置依赖于AbstractSkin
        //如果要扩展搜狗皮肤,只需在写一个AbstractSkin的子类即可
        //input.setSkin(new DefaultSkin());
        input.setSkin(new HeimaSkin());

        input.display();
    }
}

三、里氏代换原则

  • 任何基类可以出现的地方,子类一定可以出现。
  • 本质上:子类可以扩展父类的功能,但不能改变父类原有的功能。
  • 换句话说子类继承父类时,除了添加新的方法完成新增功能外,尽量不要重写父类的方法
  • 好处:
    • 如果重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差。
      特别是多态使用频繁时,程序运行出错的概率会非常大。
    • 提高继承体系的可复用性,保证使用多态时不出错。

违背里氏代换的情况

//长方形
public class Rectangle {
    private double length;
    private double width;
    
    public double getWidth() return width;
    public void setWidth(double width) this.width = width;
    public double getLength() return length;
    public void setLength(double length) this.length = length;
}
//正方形
//这里的正方形,重写了长方形中的set方法
//保证了长与宽的相等。
public class Square {
    @Override
    public void setWidth(double width) {
        super.setWidth(width);
        super.setLength(width);
    }

    @Override
    public void setLength(double length) {
        super.setLength(length);
        super.setWidth(length);
    }
}
public class RectangleDemo {
    //扩宽方法
    public static void resize(Rectangle rectangle){
        while(rectangle.getWidth() <= rectangle.getLength()){
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }
    //测试
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(10);
        rectangle.setLength(20);
        //这里对长方形进行扩宽,没有问题
        resize(rectangle);
        System.out.println("==========================");
        Square square = new Square();
        square.setWidth(10);
        //square是rectangle子类,可以使用多态
        //但由于改写了set方法,square中的长宽一直相等,导致程序陷入死循环
        //而里氏代换原则中基类出现的地方,子类一定能出现。这里显然不满足。
        //这里子类改写了父类的方法,那么在多态中容易引发错误
        resize(square);
    }
}

根据里氏代换原则改进

//四边形
public interface Quadrilateral {
    double getLength();
    double getWidth();
}
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; }
    @Override
    public double getLength() { return length; }
    @Override
    public double getWidth() { return width; }
}

四、依赖倒转原则

高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

简单的说:就是对抽象编程,不要对实现编程,这样就降低了客户与实现模块间的耦合。

组合关系: 整体类对象 能管理 部分对象的 生命周期。整体归西,部分必归西。
聚合关系: 整体类对象 能利用 部分对象,而不能管理部分对象生命周期。整体归西,部分不一定归西。

违反依赖倒转原则案例

public Class IntelCpu {
    public void run(){
        System.out.println("cpu is running...");
    }
}
public Class XiJieHardDisk {
    public void get(){
        System.out.println("getting data from XiJieHardDisk");
    }
    public void save(){
        System.out.println("save data to XiJieHardDisk");
    }
}
public Class Computer {
    private IntelCpu cpu;
    private XiJieHardDisk hardDisk;
    
    public void setIntelCpu(IntelCpu cpu) this.cpu = cpu;
    public void setXiJieHardDisk(XiJieHardDisk hardDisk) this.hardDisk = hardDisk;
    
    //这里是面向实现类编程,简单易懂,但扩展不方便
    //cpu用别的还得改代码,硬盘hardDisk也是如此
    public void run(){
        hardDisk.get();
        cpu.run();
        hardDisk.save();
    }
}

用依赖倒转原则改进

public interface Cpu {
    void run();
}
public interface HardDisk {
    void get();
    void save();
}
public Class Computer {
    //成员变量 使用接口,而不是实现类
    //所以现在可以给Cpu设置cpu接口的多种实现类了
    private Cpu cpu;
    private HardDisk hardDisk;

    public void setCpu(Cpu cpu) this.cpu = cpu;
    public void setHardDisk(HardDisk hardDisk) this.hardDisk = hardDisk;
    
    public void run(){
        hardDisk.get();
        cpu.run();
        hardDisk.save();
    }
}

五、接口隔离原则

接口隔离原则也叫最小接口原则
本质就是不应该让类*依赖它不使用的方法
一个类对另一个类的依赖应该简历在最小的接口上

  • 违背接口隔离的例子:
public class Segregation_1 {
    /**
     * 显然,
     * 类B,类D都是Interface1的实现类,都实现了Interface1的五种方法
     * 类A通过接口依赖类B,类C通过接口依赖类D
     * 如果接口对于类A和类C来说不是最小接口(即没有用到接口中的所有非default方法)
     * 那么则会违反接口隔离原则
     *
     * 使用接口隔离原则改进
     * 处理方法:把接口拆分独立的几个接口,类A和类C分别与他们需要的接口进阿里依赖关系。
     */
    interface Interface1 { void method1(); void method2(); void method3(); }

    class B implements Interface1 {
        @Override
        public void method1() { System.out.println("B-method1"); }

        @Override
        public void method2() { System.out.println("B-method2"); }

        @Override
        public void method3() { System.out.println("B-method3"); }
    }

    class D implements Interface1 {
        @Override
        public void method1() { System.out.println("D-method1"); }

        @Override
        public void method2() { System.out.println("D-method2"); }

        @Override
        public void method3() { System.out.println("D-method3"); }
    }

    //A类通过接口依赖B类,但只用到method1、method2
    class A {
        public void depend1(Interface1 interface1){ interface1.method1(); }
        public void depend2(Interface1 interface2){ interface2.method2(); }
    }

    //C类通过接口依赖D,但只用到method1、method3
    class C {
        public void depend1(Interface1 inf){ inf.method1(); }
        public void depend3(Interface1 inf){ inf.method3(); }
    }
}
  • 改进,改进的话,只需把接口拆分为多个,让实现类B、D实现多个接口即可

六、迪米特法则

迪米特法则也叫最少知识原则
只和你的直接朋友说话,不跟“陌生人”说话

  • 含义

    如果两个软件实体无须直接通信,那么就不应该发生直接的相互调用,可以通过第三方转发该调用。

  • 目的:降低类的耦合度,提高模块的相对独立性

  • 迪米特法则中的朋友是指:(这些对象存在关联、聚合或组合关系,可以直接访问这些对象的方法)

    • 当前对象本身
    • 当前对象的成员对象
    • 当前对象所创建的对象
    • 当前对象的方法参数等。

违背迪米特法则的案例:

//老板类
//此时老板需要拿到员工类,让员工打印名字。
//但员工类不是老板类的朋友,因此这个老板类的设计不符合迪米特法则。
public class Boss {
    public void  getEmloye(TeamLeader teamLeader){
        //此处的employ是其他对象方法的返回值,不属于boss的朋友
        Employe employ = teamLeader.getEmploy();
        System.out.println(employ.getName());
    }
}
//项目组长类
public class TeamLeader {
    //返回一个员工对象
    public Employe getEmploy(){
        return new Employe("张三");
    }
}
//员工类
public class Employe{
    private String name;
    public setName(String name) this.name = name;
    public getName() return this.name;
}

改进:

//老板类
public class Boss {
    public void  getEmloye(TeamLeader teamLeader){
        //同员工通信细节完全交给项目组长实现
        //老板类不直接同员工类通信
        teamLeader.getEmploy();
    }
}
//项目组长类
public class TeamLeader {
    public void getEmploy(){
        System.out.println(new Employe("张三").getName());
    }
}

七、合成复用原则

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

通常类的复用分为继承复用和合成复用两种。

继承复用虽然简单易于实现,但也存在以下缺点:

  1. 继承复用破坏了类的封装性。因为继承会将父类的试下细节暴露给子类,父类对子类是透明的,所以这种复用又称为”白箱“复用
  2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生裱花,这不利于类的扩展与维护
  3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

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

  1. 它维持了类的封装性。因为成员对象的内部细节是新对象看不见的,所以这种复用又称为”黑箱“复用
  2. 对象之间的耦合度低。可以在类的成员位置声明抽象
  3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的对象

用继承的情况

设计模式-七大设计原则

继承复用,简单易于实现,但一旦扩展,则要生成多个类。

用接口的情况

设计模式-七大设计原则

合成复用,扩展只需生成一个类

上一篇:软件设计-桥接模式


下一篇:python—列表