zoukankan      html  css  js  c++  java
  • 设计原则

            一、开闭原则 

    1.1、定义

    开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。可以提高软件系统的可复用性及可维护性。开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统,例如:我们版本更新,我尽可能不修改源代码,但是可以增加新功能。

           1.2、问题由来

    凡事的产生都有缘由。我们来看看,开闭原则的产生缘由。在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。这就对我们的整个系统的影响特别大,这也充分展现出了系统的耦合性如果太高,会大大的增加后期的扩展,维护。为了解决这个问题,故人们总结出了开闭原则。解决开闭原则的根本其实还是在解耦合。所以,我们面向对象的开发,我们最根本的任务就是解耦合。 

     1.3、解决方法

    当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

    1.4、案例

    /**
     * 定义课程接口
     */
    public interface ICourse {
        String getName();  // 获取课程名称
        Double getPrice(); // 获取课程价格
        String  getType(); // 获取课程类型
    
    
    }
    /**
     * 英语课程接口实现
     */
    public class EnglishCourse implements ICourse {
    
        private String name;
        private Double price;
        private String  type;
    
        public EnglishCourse(String name, Double price, String  type) {
            this.name = name;
            this.price = price;
            this.type = type;
        }
    
        @Override
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public Double getPrice() {
            return price;
        }
    
        public void setPrice(Double price) {
            this.price = price;
        }
    
        @Override
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    }
    // 测试
    public class Main {
        public static void main(String[] args) {
            ICourse course = new EnglishCourse("小学英语", 199D, "必修");
            System.out.println(
                    "课程名字:"+course.getName() + " " +
                            "课程价格:"+course.getPrice() + " " +
                            "课程类型:"+course.getType()
            );
        }
    }

    项目上线,课程正常销售,但是产品需要做些活动来促进销售,比如:打折。那么问题来了:打折这一动作就是一个变化,而我们要做的就是拥抱变化,现在开始考虑如何解决这个问题,可以考虑下面三种方案

    1.4.1、修改接口

      在之前的课程接口中添加一个方法 getSalePrice() 专门用来获取打折后的价格;如果这样修改就会产生两个问题,所以此方案否定

      • ICourse 接口不应该被经常修改,否则接口作为契约的作用就失去了
      • 并不是所有的课程都需要打折,加入还有语文课,数学课等都实现了这一接口,但是只有英语课打折,与实际业务不符
      • 这样一发动后,它的所有实现类都要改动,这样的代码扩展性不好
    /**
     * 定义课程接口
     */
    public interface ICourse {
        String getName();  // 获取课程名称
        Double getPrice(); // 获取课程价格
        String  getType(); // 获取课程类型
        Double getSalePrice();// 新增:打折接口
    
    }

    1.4.2、修改实现类

      在接口实现里直接修改 getPrice()方法,此方法会导致获取原价出问题;或添加获取打折的接口 getSalePrice(),这样就会导致获取价格的方法存在两个,所以这个方案也否定。

    1.4.3、通过扩展实现变化

    直接添加一个子类 SaleEnglishCourse ,重写 getPrice()方法,这个方案对源代码没有影响,符合开闭原则,所以是可执行的方案,代码如下,代码如下:

    public class SaleEnglishCourse extends EnglishCourse {
    
        public SaleEnglishCourse(String name, Double price, String author) {
            super(name, price, author);
        }
    
        @Override
        public Double getPrice() {
            return super.getPrice() * 0.85;
        }
    }
    public class Main {
        public static void main(String[] args) {
            ICourse course = new EnglishCourse("小学英语", 199D, "必修");
            System.out.println(
                    "课程名字:"+course.getName() + " " +
                            "课程价格:"+course.getPrice() + " " +
                            "课程类型:"+course.getType()
            );
            ICourse saleEnglishCourse = new SaleEnglishCourse ("小学英语", 199D, "必修");
            System.out.println(
                    "课程名字:"+saleEnglishCourse.getName() + " " +
                            "课程价格:"+saleEnglishCourse.getPrice() + " " +
                            "课程类型:"+saleEnglishCourse.getType()
            );
        }
    }

    1.5、小结

    开闭原则具有理想主义的色彩,说的很抽象,它是面向对象设计的终极目标。其他几条原则,则可以看做是开闭原则的实现。我们要用抽象构建框架,用实现扩展细节。

    二、依赖倒置原则

    2.1、定义

    依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。

    2.2、问题由来

    类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

    2.3、解决方法

    将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。 在实际编程中,我们一般需要做到如下3点:

    1). 低层模块尽量都要有抽象类或接口,或者两者都有。

    2). 变量的声明类型尽量是抽象类或接口。

    3). 使用继承时遵循里氏替换原则。 

    采用依赖倒置原则尤其给多人合作开发带来了极大的便利,参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。

    2.4、案例 

    写一个关于司机开车的示例,假设我们现在有个司机,可以开车,然后有一辆奔驰,于是就可以让车跑起来。

    public class Driver {
        //    司机职责就是驾驶汽车
        public void drive(Benz benz) {
            benz.run();
        }
    }
    
    public class Benz {
        //    车的作用就是跑
        public void run() {
            System.out.println("奔驰车跑起来了");
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            Driver zhangsan = new Driver();
            Benz benz = new Benz();
            // 张三开车
            zhangsan.drive(benz);
        }
    }

    以上代码就可以让张三把奔驰车开起来,可是这样子就有一个问题,如果现在有一辆宝马:

    public class BMW {
        //    车的作用就是跑
        public void run() {
            System.out.println("宝马车跑起来了");
        }
    }

    我们发现张三并不能让宝马车跑起来,因为Driver依赖Benz太紧,当多了BMW后无法扩展。因此针对接口编程,依赖于抽象而不依赖于具体。如下代码所示:

    public interface IDriver {
        //    司机职责就是驾驶汽车
        public void drive(Icar car);
    }
    
    public class Driver implements IDriver{
        //    司机职责就是驾驶汽车
        public void drive(Icar car) {
            car.run();
        }
    }
    
    public interface Icar {
        //    车的作用就是跑
        public void run();
    }
    
    public class Benz implements Icar {
        //    车的作用就是跑
        public void run() {
            System.out.println("奔驰车跑起来了");
        }
    }
    
    public class BMW implements Icar {
        //    车的作用就是跑
        public void run() {
            System.out.println("宝马车跑起来了");
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            IDriver zhangsan = new Driver();
            Benz benz = new Benz();
            zhangsan.drive(benz);
    
            BMW bmw = new BMW();
            zhangsan.drive(bmw);
        }
    }

    这样张三就直接开走了奔驰和宝马,要是接下来再来一辆奥迪,那么原有的所有代码都无需改动,只用添加一辆奥迪的车即可。

    2.5、小结

    依赖倒置原则就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。 

     三、单一职责原则

    3.1、定义

    单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。假设我们有一个 Class 负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障。这样一来,这个 Class 存在两个导致类变更的原因。如何解决这个问题呢?我们就要给两个职责分别用两个 Class 来实现,进行解耦。后期需求变更维护互不影响。这样的设计,可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。总体来说就是一个 Class/Interface/Method 只负责一项职责。

    3.2、问题由来

    类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

    3.3、解决方法

    分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

    3.4、案例

           最近一直陪媳妇去医院检查,下面就医院举例,医院体检会分为不同类别的科室,例如一般检查室(身高,体重,视力等),抽血室,内科,外科,五官科,妇科,眼科等等,这些科室单一的检查人体的某一部位或者某几个部位,那么,为什么不将检查的所有内容分到一个科室,一次性检查完,其实这样会造成大家排队时间长,不能交替的进行体检,当 检查者一个项目没有完成的时候,还不能较好的进行其他项目的体检。并且每个体检科室一般不具有紧密的联系,把每个人体所需检查的部位划分为若干单元,每个单元的医师做自己份内的事情,还有助于体检高效的进行。

           在软件开发设计的时候也是如此,对象包括对象的属性和方法,当我们设计接口的时候,有时候会出现有些问题,比如人的属性和行为全都放在一个接口中声明,这样就造成了业务对象和业务方法被放在了一起,接口有两种职责,违背了单一职责原则,正如下面的例子:
    public interface Iexamine{
    
        //检查身高
        void setHeight(double height);
    
        double getHeight();
        //检查体重
        void setWeight(double weight);
    
        double getWeight();
        //心率是否正常
        boolean testHeartRate(int  number);
        
    }

    以上的例子身高和体重本应该在一般体检科检查,而心率检查一般在内科,但是这就会给人一种不知道这个接口到底是做什么的感觉,职责不清晰,到底是一般检查科还是内科,后期维护的时候也会造成各种各样的问题。因此,解决办法是:单一职责原则,将这个接口分解成两个职责不同的接口即可。

    public interface IGerExamine {
        //检查身高
        void setHeight(double height);
    
        double getHeight();
        //检查体重
        void setWeight(double weight);
    
        double getWeight();
    }
    public interface IHeartRateExamine {
        //心率是否正常
        boolean testHeartRate(int  number);
    }

    3.5、小结

    这样就实现了接口的单一职责。那么实现接口的时候,就需要有两个不同的类。

    四、接口隔离原则

    4.1、定义

    接口隔离原则(Interface Segregation Principle, ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。这个原则指导我们在设计接口时应当注意一下几点:
    1、一个类对一类的依赖应该建立在最小的接口之上。
    2、建立单一接口,不要建立庞大臃肿的接口。
    3、尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)。
    接口隔离原则符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。我们在设计接口的时候,要多花时间去思考,要考虑业务模型,包括以后有可能发生变更的地方还要做一些预判。所以,对于抽象,对业务模型的理解是非常重要的。

    4.2、问题由来

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

    4.3、解决方法

    1、 使用委托分离接口。2、 使用多重继承分离接口。3.将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

    4.4、案例

    下面我们来看张图,一切就一目了然了。

    这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法

    修改后:

     如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口

    4.5、小结

    我们在代码编写过程中,运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。设计接口的时候,只有多花些时间去思考和筹划,就能准确地实践这一原则。 

    五、迪米特法则

    5.1、定义

    迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。迪米特原则主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类。

    5.2、问题由来

    类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。

    5.3、解决方法

    尽量降低类与类之间的耦合。 自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。 

    迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系。故过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

    5.4、案例

    如一个明星与经纪人的关系实例。明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图所示。

    复制代码
    public class LoDtest
    {
        public static void main(String[] args)
        {
            Agent agent=new Agent();
            agent.setStar(new Star("林心如"));
            agent.setFans(new Fans("粉丝"));
            agent.setCompany(new Company("中国传媒有限公司"));
            agent.meeting();
            agent.business();
        }
    }
    //经纪人
    class Agent
    {
        private Star myStar;
        private Fans myFans;
        private Company myCompany;
        public void setStar(Star myStar)
        {
            this.myStar=myStar;
        }
        public void setFans(Fans myFans)
        {
            this.myFans=myFans;
        }
        public void setCompany(Company myCompany)
        {
            this.myCompany=myCompany;
        }
        public void meeting()
        {
            System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了。");
        }
        public void business()
        {
            System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽淡业务。");
        }
    }
    //明星
    class Star
    {
        private String name;
        Star(String name)
        {
            this.name=name;
        }
        public String getName()
        {
            return name;
        }
    }
    //粉丝
    class Fans
    {
        private String name;
        Fans(String name)
        {
            this.name=name;
        }
        public String getName()
        {
            return name;
        }
    }
    //媒体公司
    class Company
    {
        private String name;
        Company(String name)
        {
            this.name=name;
        }
        public String getName()
        {
            return name;
        }
    }

    5.5、总结

    核心是只与直接的朋友通信

    六、里氏替换原则 

    6.1、定义

    里氏替换原则(Liskov Substitution Principle,LSP)是指如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。定义看上去还是比较抽象,重新理解一下,可以理解为一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。根据这个理解,总结一下:
    引申含义:子类可以扩展父类的功能,但不能改变父类原有的功能。
    1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    2、子类中可以增加自己特有的方法。
    3、当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
    4、当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等

    6.2、问题由来

    有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障

    6.3、解决方法

    类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法 

    6.4、案例

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

    子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。举例:

    public class C {
        public int func(int a, int b){
            return a+b;
        }
    }
    
    public class C1 extends C{
        @Override
        public int func(int a, int b) {
            return a-b;
        }
    }
    
    public class Client{
        public static void main(String[] args) {
            C c = new C1();
            System.out.println("2+1=" + c.func(2, 1));
        }
    }

    上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。

    6.4.2、子类可以有自己的个性

    在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:

    public class C {
        public int func(int a, int b){
            return a+b;
        }
    }
    
    public class C1 extends C{
        public int func2(int a, int b) {
            return a-b;
        }
    }
    
    public class Client{
        public static void main(String[] args) {
            C1 c = new C1();
            System.out.println("2-1=" + c.func2(2, 1));
        }
    }

    6.4.3、覆盖或实现父类的方法时输入参数可以被放大

    当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,通过代码来讲解一下:

    public class ParentClazz {
        public void say(CharSequence str) {
            System.out.println("parent execute say " + str);
        }
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            ParentClazz parent = new ParentClazz();
            parent.say("hello1");
            ChildClazz child = new ChildClazz();
            child.say("hello");
    
        }
    }
     class ChildClazz extends ParentClazz {
        public void say(String str) {
            System.out.println("child execute say " + str);
        }
    }

    以上代码中我们并没有重写父类的方法,只是重载了同名方法,具体的区别是:子类的参数 String 实现了父类的参数 CharSequence。此时执行了子类方法,在实际开发中,通常这不是我们希望的,父类一般是抽象类,子类才是具体的实现类,如果在方法调用时传递一个实现的子类可能就会产生非预期的结果,引起逻辑错误,根据里氏替换的子类的输入参数要宽于或者等于父类的输入参数,我们可以修改父类参数为String,子类采用更宽松的 CharSequence,如果你想让子类的方法运行,就必须覆写父类的方法。代码如下:

    public class ParentClazz {
        public void say(String  str) {
            System.out.println("parent execute say " + str);
        }
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            ParentClazz parent = new ParentClazz();
            parent.say("hello1");
            ChildClazz child = new ChildClazz();
            child.say("hello");
    
        }
    }
     class ChildClazz extends ParentClazz {
        public void say(Object str) {
            System.out.println("child execute say " + str);
        }
    }

    6.4.4、覆写或实现父类的方法时输出结果可以被缩小

    当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。代码实现案例如下:

    public abstract class Father {
        public abstract Map hello();
        public static void main(String[] args) {
            Father father = new Son();
            father.hello();
        }
    }
    class Son extends Father {
        @Override
        public Map hello() {
            HashMap map = new HashMap();
            System.out.println("son execute");
            return map;
        }
    }

    6.5、总结

    保证了父类的复用性,同时也能够降低系统出错误的故障,防止误操作,同时也不会破坏继承的机制,这样继承才显得更有意义。增强程序的健壮性,版本升级是也可以保持非常好的兼容性.即使增加子类,原有的子类还可以继续运行.在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑,完美!

    继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。里氏替换原则的目的就是增强程序健壮性,版本升级时也可以保持非常好的兼容性。有人会说我们在日常工作中,会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?后果就是:你写的代码出问题的几率将会大大增加

    七、合成复用原则

    7.1、定义

    也有人叫做合成复用原则,及尽量使用合成/聚合,尽量不要使用类继承。换句话说,就是能用合成/聚合的地方,绝不用继承。

    7.2、为什么要尽量使用合成/聚合而不使用类继承?

    1. 对象的继承关系在编译时就定义好了,所以无法在运行时改变从父类继承的子类的实现

    2. 子类的实现和它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化

    3. 当你复用子类的时候,如果继承下来的实现不适合解决新的问题,则父类必须重写或者被其它更适合的类所替换,这种依赖关系限制了灵活性,并最终限制了复用性。

    7.3、案例

    以汽车分类管理程序为例来介绍合成复用原则的应用。

    分析:汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。如图所示是用继淨:关系实现的汽车分类的类图。

     从图中可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。但如果改用组合关系实现就能很好地解决以上问题,其类图如下图所示。 

     7.4、总结

    这些原则在设计模式中体现的淋淋尽致,设计模式就是实现了这些原则,从而达到了代码复用、增强了系统的扩展性。所以设计模式被很多人奉为经典。我们可以通过好好的研究设计模式,来慢慢的体会这些设计原则。

    八、七大原则总结

    设计原则 归纳 目的
    开闭原则 对扩展开放,对修改关闭 降低维护带来的风险
    依赖倒置原则 高层不应该依赖于低层 更利于代码结构的升级扩展
    单一职责原则 一个类只做一件事 便于理解,提高代码的可读性
    接口隔离原则 一个接口只做一件事 功能解耦,高聚合,低耦合
    迪米特法则 不和陌生人做朋友 只和朋友交流,不和陌生人做朋友,减少代码臃肿
    里氏替换原则 子类重写 方法功能发生改变,不应该影响父类方法 防止继承泛滥
    合成复用原则 尽量使用组合实现代码复用,而不使用继承 降低代码耦合
    这短短的一生我们最终都会失去,不妨大胆一点,爱一个人,攀一座山,追一个梦
  • 相关阅读:
    android连接wifi模块
    idea 控制台乱码debug
    线程控制
    jvm 垃圾回收
    java 单例模式
    http报头
    java 构造函数 , 参数传递 , lamda表达式
    mysql 杂识
    spring mvc 配置 拦截器不起作用
    spring mvc 每次请求后是否自动销毁线程
  • 原文地址:https://www.cnblogs.com/xing1/p/14531096.html
Copyright © 2011-2022 走看看