设计模式(0)—— 设计模式基石 —— 软件设计七大原则

版权声明:如有任何疑问,欢迎@我:[email protected] https://blog.csdn.net/qq_37206105/article/details/83961470

软件设计七大原则。在面向对象软件开发过程中,我们适当的遵循一定的设计原则,对于开发来说,能够提高代码的健壮性,复用性,可维护性。同时利于团队协作交流。

本文中的部分原则给出一个特定场景和具体代码实现。其它的设计原则贴出他人写的博文地址。

1. 开闭原则(open close)

 定义: 软件实体(类,模块,函数)应该对扩展开放,对修改关闭。
 解释:用抽象构建框架,用实现扩展细节。
 优点:提高系统的可复用性,可维护性。
  • Code实现

    以书店的书籍作为一个简单的例子

    书籍有3个简单属性:id,name,price。于是对于访问书籍的信息我们抽象 出一个接口类:

    public interface IBook {
    	Integer getId();
    	String  getName();
    	double  getPrice();
    }
    

    现在有关IT类的书。我们自然就可以实现这个接口:

    public class ITBook implements IBook{
    
        private Integer Id;
        private String name;
        private double price;
    
        @Override
        public Integer getId() {
            return this.Id;
        }
        
        @Override
        public String getName() {
            return this.name;
        }
    	@Override
    	public Double getPrice(){
    		return this.price;
    	}
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            IBook cpp = new ITBook( 11, "CPP", 32.0);
            //print : id,name,price
            System.out.println( "Id: " + cpp.getId() + ", name: " + cpp.getName() + ", price:" + cpp.getPrice());
        }
    }
    

    现正逢双11,IT类的书打折(discount)。我们要对上面类的设计做出一些变化。
    为此我们可以尝试着对IBook类修改,增加一个抽象函数:

    public interface IBook {
    	Integer getId();
    	String  getName();
    	double  getPrice();
    	//添加的代码
    	double  getDiscountPrice();
    }
    

    那么我们的ITBook类必须要override这个函数。对于一个书店来说,肯定不止IT类的书,还有文学,艺术,工程等等不同类的书。这些类按照我们的规则来设计的话,一定也会implements IBook。如此一来,我们在IBook这个底层接口内修改代码实在不是一个好的建议,因为一旦我们修改了底层IBook类的代码,所有实现IBook接口的类都必须进行相应的修改。

    按照开闭原则:用抽象构建框架,用实现扩展细节
    在我们的例子中,我们用抽象搭建的框架就是IBook这个类,打折这个功能相当于是扩展。我们不应该修改用抽象封装好的底层框架(对应于关闭);而是应该在凌驾于底层框架之上,进行功能的扩展(对应于开放)

    说起来也挺容易理解:上层的应用都建立在底层之上,如果底层的接口修改了,那么所有依赖于这个这些接口的类都需要进行修改,否则会报错。

    扫描二维码关注公众号,回复: 4118896 查看本文章

    回到例子中。根据分析,我们不修改IBook这个底层接口类,而是修改上层的实体类,实现打折功能的扩展。

    新定义一个类ITDiscountBook继承自ITBook:

    public class ITDiscountBook extends ITBook {
    
        private double discount;
    
        public ITDiscountBook(Integer id, String name, double price, double discount) {
            super(id, name, price);
            this.discount = discount;
        }
    
        public double getDiscountPrice(){
            return super.getPrice() * discount;
        }
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            //IBook cpp = new ITBook( 11, "CPP", 32.0);
            //System.out.println( "Id: " + cpp.getId() + ", name: " + cpp.getName() + ", price:" + cpp.getPrice() );
    
            IBook cppDiscountBook = new ITDiscountBook(11, "cpp", 32.0, 0.8);
            //注意 类的转换
            System.out.println( "id:" + cppDiscountBook.getId() + ", name:" +  cppDiscountBook.getName() +
                                ", Original price: " + cppDiscountBook.getPrice() +
                                " Discount price: " + ((ITDiscountBook) cppDiscountBook).getDiscountPrice() );
        }
    }
    
  • 类关系图(Diagram)
    开闭原则实例图

2. 依赖倒置(dependency inversion)

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象
解释:
	 抽象不应该依赖细节,细节应该依赖抽象
	 针对接口编程,不要针对实现编程
优点:减少类间的耦合性,提高系统稳定性,提高代码可读性,可维护性。降低修改程序所造成的风险。
  • Code实现
    围绕书店这个例子,我们定义一个顾客类(Customer),它能实现买(buy)的功能。顾客买不同类型的书籍能够触发不同的模块调用。假设现在书店里面有三类书:IT,Art,Literature。Customer类和测试类Test实现如下:

    // 顾客类(低层模块):对于不同的行为定义不同的模块
    public class Customer {
    
       public void buyITBook(){
           System.out.println("Buy a IT Book.");
       }
    
       public void buyArtBook(){
           System.out.println("Buy a Art Book.");
       }
    
        public void buyLiteratureBook(){
            System.out.println("Buy a Literature Book.");
        }
    }
    //测试类(高层模块):测试类(高层)的功能依赖Customer(低层次)的具体实现
    public class Test {
        public static void main(String[] args) {
            Customer cus = new Customer();
            cus.buyITBook();
            cus.buyArtBook();
            cus.buyLiteratureBook();
        }
    }
    

    从上面的例子中可以看到,Customer类的设计使其扩展性很差,外部(Test)需要添加什么功能,都需要重新在Customer类内部添加或者修改相应的代码。也就是说高层模块依赖低层模块

    现在我们尝试修改代码,使其符合我们所说的依赖倒置原则。

    我们把这几个Customer内部的功能模块抽离出来,使它们成为三个不同的类,然后Customer类只需要根据这三个类的统一接口触发模块调用即可。如下面类关系图所示:
    依赖倒置例类图

    下面是抽离出来的代码:

    // 接口类,定义三个类的统一触发行为方法
    public interface IBook {
        void buyBook();
    }
    

    /**********************三个实体类*************************/

    // ITBook类
    public class ITBook implements IBook {
        @Override
        public void buyBook() {
            System.out.println("Buy a IT Book.");
        }
    }
    
    // ArtBook 类
    public class ArtBook implements IBook {
        @Override
        public void buyBook() {
            System.out.println("Buy a Art Book.");
        }
    }
    
    // LiteratureBook 类
    public class LiteratureBook implements IBook {
        @Override
        public void buyBook() {
            System.out.println("Buy a Literature Book.");
        }
    }
    

    /*******************Customer类********************/

    public class Customer {
        public void buyABook(IBook iBook){
            iBook.buyBook();
        }
    }
    

    /****************测试Test类**************/

    public class Test {
        public static void main(String[] args) {
            Customer cus = new Customer();
            cus.buyABook( new ITBook() );
            cus.buyABook( new ArtBook() );
            cus.buyABook( new LiteratureBook() );
        }
    }
    

    可以看到代码的精彩之处在于,我们在外部(Test类)让Customer类执行相应行为时,只需要传入相应接口类就行,对Customer内部的实现丝毫没有影响。Customer跟另外三个类的唯一的通信渠道,就是通过一个接口类来作为函数参数,尽量降低耦合。

    同时该设计的扩展性是良好的,当我们需要添加新的行为时(在这里就是买新的种类的书),我们只需要再创建一个类,使这个类实现IBook接口就行。

    另外,Customer的几个实现版本总结如下:

    						/*1个原始版本不符合依赖倒置原则*/
    public class Customer {
       public void buyITBook(){
           System.out.println("Buy a IT Book.");
       }
       public void buyArtBook(){
           System.out.println("Buy a Art Book.");
       }
       public void buyLiteratureBook(){
           System.out.println("Buy a Literature Book.");
       }
    }
    						/*3个符合依赖倒置原则的设计方式*/
    // 接口传参注入
    public class Customer {
        public void buyABook(IBook iBook){
            iBook.buyBook();
        }
    }
    // 构造器注入(单例)
    public class Customer {
    	private IBook iBook;
        public void Customer (IBook iBook){
            this.iBook = iBook;
        }
        public void buyABook(){
    		iBook.buyBook();
    	}
    }
    // setter注入
    public class Customer {
    	private IBook iBook;
        public void setIBook(IBook iBook){
            this.iBook = iBook;
        }
    }
    

3. 单一职责原则(single responsibility)

定义:不要存在多于一个导致类变更的原因
解释:一个类/接口/方法只负责一项职责
优点:降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险

点击借鉴

4. 接口隔离原则(Interface isolation)

定义:
	用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口
解释:
	一个类对一个类的以来应该建立在最小的接口上
	建立单一接口,不要建立庞大臃肿的接口
	尽量细化接口,接口中的方法尽量少
原则:
	注意适度原则,一定要适度
优点:
	符合高内聚,低耦合的设计思想,从而使得类具有很好的可读性,可扩展性,可维护型。

点击借鉴

5. 迪米特原则(demeter)

定义
	一个对象应该对其它对象保持最小的了解,又叫最小知道原则
解释
	尽量降低类之间的耦合
原则
	强调只和朋友交流,不和陌生人说话(朋友:出现在成员变量,方法的输入,输出参数中的类称为朋友类。而出现在方法体内部的类不属于朋友类。)
优点
	降低类间耦合

点击借鉴

6. 里氏替换原则(Liskov Substitution)

定义
	如果对每个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为都没有发生变化,那么类型T2是类型T1的子类型
定义扩展
	一个软件实体如果使用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
引申意义
	子类可以扩展父类的功能,但不能改变父类原有的功能
	含义
		子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
		子类中可以增加自己特有的方法。
		当子类的方法重载父类的方法时,方法的前置条件(入参)要比父类方法的入参更宽松
		当子类的方法实现父类的方法时,方法的后置条件(返回值)要比父类更严格或相等。
优点
	约束继承泛滥,开闭原则的一种体现
	加强程序的健壮性,同时变更时也可以做到非常好的兼容性。提高程序的维护性,扩展性。降低需求变更时引入的风险

[点击借鉴](https://blog.csdn.net/Bitou_Von/article/details/4210654)

7. 组合复用原则(composition aggregation)

定义
	尽量使用对象组合或聚合,而不是继承关系达到软件复用的目的
解释
	聚合
		has--A。电脑与U盘
	组合
		contains--A。国家:国与家
	继承
		is--A。人与员工
优点
	使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其它类造成的影响相对较小

点击借鉴

猜你喜欢

转载自blog.csdn.net/qq_37206105/article/details/83961470