七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码

七大设计原则的简单解释(包含合成复用原则),简单理解、快速入门,具备案例代码

一、Open-Closed Principle开闭原则

(一)、开闭原则定义

​ 开闭原则是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。可以提高软件系统的可复用性以及可维护性。

​ 开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立文档灵活的系统,例如:我们进行版本更新时,尽可能不修改源代码,但是可以增加新功能。

​ 开闭原则就是在我们需要发生变化时,应该是进行代码的拓展,而不是进行修改源码。实现开闭原则的核心思想就是面向抽象编程。

(二)、开闭原则优点

​ (1)、可复用性高:灵活的增加新功能

​ (2)、可维护性高:不需要修改底层源码,只需要对相应功能进行更新

(三)、案例代码

1、书籍接口代码

public interface Book {
    
    
    public String getName();
    public String getAuthor();
    public Double getPrice();
}

2、Java类书籍

public class JavaBook implements Book{
    
    
    private String name;
    private String author;
    private Double price;

    public JavaBook(String name, String author, Double price) {
    
    
        this.name = name;
        this.author = author;
        this.price = price;
    }

    @Override
    public String getName() {
    
    
        return this.name;
    }

    @Override
    public String getAuthor() {
    
    
        return this.author;
    }

    @Override
    public Double getPrice() {
    
    
        return this.price;
    }
}

3、Java类折扣书籍

​ 当我们需要折扣出售书籍时,我们并非在JavaBook的基础上修改getPrice方法中的源码,这样会导致我们无法获得原价,此时我们需要进行添加多一个方法,并且由于以后折扣出售会有不同的处理方案,所以我们应选择拓展这个类。

public class JavaDiscountPrice extends JavaBook{
    
    
    public JavaDiscountPrice(String name, String author, Double price) {
    
    
        super(name, author, price);
    }

    public Double getJavaDiscountPrice() {
    
    
        return super.getPrice()*0.5;
    }
}

4、增加JavaDiscountPrice类的前后区别

public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 直接在JavaBook上进行修改
        JavaBook javaBook = new JavaBook("java书籍", "无名氏", 12.00);
        // 只可得知折扣后的价格
        System.out.println("折扣" + javaBook.getPrice());
        // 添加JavaDiscountPrice
        JavaDiscountPrice javaDiscountPrice = new JavaDiscountPrice("java书籍", "无名氏", 12.00);
        // 可得知原价以及折扣后的价格
        System.out.println("原价:" + javaBook.getPrice());
        System.out.println("折扣:" + javaDiscountPrice.getPrice());
    }
}

二、Dependence Inversion Principle依赖倒置原则

(一)、依赖倒置原则定义

​ 依赖倒置原则是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象。

​ 通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。

(二)、依赖倒置原则优点

​ (1)、可以减少类间的耦合性。

​ (2)、提高代码的可读性和可维护性,降低修改代码所带来的风险。

(三)、案例代码

1、不满足依赖倒置原则的情况

​ 随着顾客打算购买的书籍的增多或者变化,我们将不断增加或改变底层代码,同时在高层也需要追加调用,这样导致我们的代码越来越臃肿,同时也难以维护,实际上也是非常不稳定的,在修改代码的同时会带来意想不到的风险。

public class Customer {
    
    

    public void getJavaBook() {
    
    
        System.out.println("购买了java语言书");
    }

    public void getCBook() {
    
    
        System.out.println("购买了C语言书");
    }

    public void getPythonBook() {
    
    
        System.out.println("购买了Python语言书");
    }

    public static void main(String[] args) {
    
    
        Customer customer = new Customer();
        customer.getCBook();
        customer.getJavaBook();
        customer.getPythonBook();

    }
}

2、接口传递方式进行依赖注入

(1)、书籍抽象接口类
public interface Book {
    
    
    void getBook();
}
(2)、Java类书籍实现类
public class JavaBook implements Book{
    
    
    @Override
    public void getBook() {
    
    
        System.out.println("购买了Java类书籍");
    }
}
(3)、Python类书籍实现类
public class PythonBook implements Book{
    
    
    @Override
    public void getBook() {
    
    
        System.out.println("购买了Python类书籍");
    }
}
(4)、接口传递方式调用
public class PurchaseBook {
    
    
    private Book book;

    public void getBook(Book book){
    
    
        book.getBook();
    }

    public static void main(String[] args) {
    
    
        PurchaseBook purchaseBook = new PurchaseBook();
        purchaseBook.getBook(new JavaBook());
        purchaseBook.getBook(new PythonBook());
    }
}

3、构造器方式进行依赖注入

public class PurchaseBook {
    
    
    private Book book;

    PurchaseBook(Book book) {
    
    
        this.book = book;
    }

    public void getBook() {
    
    
        book.getBook();
    }

    public static void main(String[] args) {
    
    
        PurchaseBook purchaseBook = new PurchaseBook(new JavaBook());
        purchaseBook.getBook();
    }
}

4、set方式进行依赖注入

public class PurchaseBook {
    
    
    private Book book;

    public void setBook(Book book){
    
    
        this.book = book;
    }

    public void getBook(){
    
    
        book.getBook();
    }

    public static void main(String[] args) {
    
    
        PurchaseBook purchaseBook = new PurchaseBook();
        purchaseBook.setBook(new JavaBook());
        purchaseBook.getBook();
    }
}

三、 Simple Responsibility Principle单一职责原则

(一)、单一职责原则定义

​ 单一职责原则是指不要存在多一个一个导致类变更的原因。无论是接口、方法、或者类,我们在编写的时候都有可能会涉及到许多的业务逻辑,我们应该尽量将业务逻辑一致的放在一起,其他的进行分开,这样子就不会使一个接口、方法、类去处理多个职责,而在发生代码变更的时候相互影响。

​ 总体来说就是一个Class/Interface/Method只负责一项职责。

(二)、单一职责原则优点

​ (1)、降低类的功能复杂度

(2)、提高系统的可维护性

(3)、变更风险低

(三)、案例代码

​ 书的进货和卖出是两种业务,所以分成两个接口,后续业务扩展由两类接口伸展

// 进货
public interface EnterBook {
    
    
    void enterBook();
}
// 卖货
public interface SellingBook {
    
    
    void sellingBook();
}

四、Interface Segregation Principle接口隔离原则

(一)、接口隔离原则定义

​ 接口隔离原则是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖他不需要的接口。

  • 一个类对一类的依赖应该建立在最小的接口之上

  • 建立单一接口不要建立庞大臃肿的接口

  • 尽量细化接口,接口中的方法尽量少

(二)、接口隔离原则优点

​ (1)、符合高内聚,低耦合

​ (2)、具有很好的可读性、可扩展性和可维护性

(三)、案例代码

1、不满足接口隔离原则的情况

Animal接口:一个动物行为接口,包含吃、飞以及游泳行为

public interface Animal {
    
    
    void eat();

    void fly();

    void swim();
}

Bird类实现:

public class Bird implements Animal{
    
    
    @Override
    public void eat() {
    
    

    }

    @Override
    public void fly() {
    
    

    }

    @Override
    public void swim() {
    
    

    }
}

Dog类实现:

public class Dog implements Animal{
    
    
    @Override
    public void eat() {
    
    

    }

    @Override
    public void fly() {
    
    

    }

    @Override
    public void swim() {
    
    

    }
}

此时可发现,Bird类的swim方法只可空置,同样Dog类的fly方法也只可空置。

2、符合接口隔离原则的情况

EatAnimal接口:动物吃行为

public interface EatAnimal {
    
    
    void eat();
}

FlyAnimal接口:动物飞行为

public interface FlyAnimal {
    
    
    void fly();
}

SwimAnimal接口:动物游泳行为

public interface SwimAnimal {
    
    
    void swim();
}

Dog类只实现EatAnimal和SwimAnimal接口:

public class Dog implements SwimAnimal,EatAnimal{
    
    
    @Override
    public void eat() {
    
    

    }

    @Override
    public void swim() {
    
    

    }
}

五、Law of Demeter 迪米特法则

(一)、迪米特原则定义

​ 迪米特原则是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则。尽量降低类与类之间的耦合。迪米特原则主要强调只和朋友交流,不和陌生人说话。

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

(二)、迪米特原则优点

​ (1)、降低了类之间的耦合度,提高了模块的相对独立性。

​ (2)、由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

(三)、案例代码

1、不符合迪米特原则的情况

​ 此时A不认识C只认识B,而B认识C。虽然实现了play(),但是不符合迪米特原则

public class A {
    
    
    // A认识B,但是不认识C;而B认识C。
    B b = new B();

    public void play() {
    
    
        System.out.println("play");
        b.play();
        C c = new C();
        c.play();
    }

    public static void main(String[] args) {
    
    
        A a = new A();
        a.play();
    }
}

2、符合迪米特原则的情况

因为A认识B,所以只同B联系

public class A {
    
    
    B b = new B();

    public void play() {
    
    
        System.out.println("Aplay");
        b.play();
    }

    public static void main(String[] args) {
    
    
        A a = new A();
        a.play();
    }
}

因为B认识C,所以通过B来联系C,而不是通过A直接联系C

public class B {
    
    
    C c = new C();

    public void play() {
    
    
        System.out.println("Bplay");
        c.play();
    }
}

六、Liskov Substitution Principle里氏替换原则

(一)、里氏替换原则定义

​ 里氏替换原则是指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。

​ 简单来说便是,继续必须确保超类所拥有的性质在子类中仍然成立。

  • 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
  • 子类中可以增加自己特有的方法。
  • 当子类重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

(二)、里氏替换原则优点

​ (1)、里氏替换原则是实现开闭原则的重要方式之一。

​ (2)、它克服了继承中重写父类造成的可复用性变差的缺点。

​ (3)、它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

(三)、案例代码

1、不符合里氏替换原则的情况

长方形的类:

public class Rectangle {
    
    
    private long width;
    private long height;

    public long getWidth() {
    
    
        return width;
    }

    public void setWidth(long width) {
    
    
        this.width = width;
    }

    public long getHeight() {
    
    
        return height;
    }

    public void setHeight(long height) {
    
    
        this.height = height;
    }
}

正方形的类:继承了长方形,并且改变了长方形的方法

public class Squre extends Rectangle {
    
    
    private long length;

    public long getLength() {
    
    
        return length;
    }

    public void setLength(long length) {
    
    
        this.length = length;
    }

    @Override
    public long getWidth() {
    
    
        return length;
    }

    @Override
    public void setWidth(long width) {
    
    
        setLength(width);
    }

    @Override
    public long getHeight() {
    
    
        return length;
    }

    @Override
    public void setHeight(long height) {
    
    
        setLength(height);
    }
}

错误情况:检查长方形的长是否大于宽,如果没有就需要加1直到长大于宽,但是因为正方形是长方形的子类,所以传正方形的类依然可以成立,但是方法执行是死循环因为长和宽永远相等。

public class Check {
    
    
    public void resize(Rectangle rectangle){
    
    
        while(rectangle.getWidth()>=rectangle.getHeight()){
    
    
            rectangle.setHeight(rectangle.getHeight()+1);
            System.out.println("长"+rectangle.getHeight()+"宽"+rectangle.getWidth());
        }
        System.out.println("长"+rectangle.getHeight()+"宽"+rectangle.getWidth());
    }

    public static void main(String[] args) {
    
    
        Squre squre = new Squre();
        squre.setLength(10);
        Check check = new Check();
        check.resize(squre);
    }
}

2、里氏替换原则的方法的前置条件的范围不同的区别

​ 当子类重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。可以自己通过写代码验证

​ 你会发现执行结果依然是父类的,但一旦你的前置条件比父类方法更严格就相当于是子类的方法,执行的方法不同。子类可以扩展方法但不得重写或覆盖已经实现的方法

​ 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。这个当你用代码实现的时候如果不满足此条件编译器都会报错。

public class Parent {
    
    
    public void method(HashMap hashMap){
    
    
        System.out.println("父类方法执行");
    }
}
public class Son extends Parent{
    
    

    public void method(Map hashMap) {
    
    
        System.out.println("子类方法执行");
    }

    public static void main(String[] args) {
    
    
        Son son = new Son();
        HashMap hashMap = new HashMap();
        son.method(hashMap);
    }

}

七、Composite&Aggregate Reuse Principle合成复用原则

(一)、合成复用原则定义

​ 合成复用原则是指尽量使用对象组合(has-a)/聚合(contanis-a),而不是继承关系达到软件复用的目的,可以使得系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。

​ 继承称之为白箱复用,相当于把所有的细节都暴露给子类。组合/聚合称之为黑箱复用,对类之外的对象是无法获取到细节的。

(二)、合成复用原则优点

​ (1)、它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。

​ (2)、新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。

​ (3)、复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

(三)、案例代码

public interface DBconnect {
    
    
    String getConnect();
}
public class MysqlConnect implements DBconnect {
    
    
    public String getConnect() {
    
    
        return "mysql连接";
    }
}
public class ProductConnect {
    
    
    private DBconnect dBconnect ;

    public void setdBconnect(DBconnect dBconnect) {
    
    
        this.dBconnect = dBconnect;
    }

    public void addConnect(){
    
    
        System.out.println(dBconnect.getConnect());
    }

    public static void main(String[] args) {
    
    
        ProductConnect productConnect = new ProductConnect();
        productConnect.setdBconnect(new MysqlConnect());
        productConnect.addConnect();
    }
}

八、设计模式的相关博客文章链接

1、工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂)

链接: 工厂模式详解附有代码案例分析(简单工厂,工厂方法,抽象工厂).

2、单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式)

链接: 单例模式详解及代码案例与应用场景(饿汉式单例模式、懒汉式单例模式、注册式单例模式).

3、原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析)

链接: 原型模式详解附有代码案例分析(浅克隆和深克隆的相关解析).

4、建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析)

链接: 建造者模式详解附有代码案例分析(包含建造者模式与工厂模式的区别分析).

猜你喜欢

转载自blog.csdn.net/hyyyya/article/details/108339550