SOLID

来源:互联网 发布:华为分享网络wifi密码 编辑:程序博客网 时间:2024/05/22 14:02

单一职责原则(SRP

 

当需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题,非常耗时耗力

示例:

新建一个Rectangle类,该类包含两个方法,一个用于把矩形绘制在屏幕上,一个方法用于计算矩形的面积。如图

 

Rectangle类违反了SRP原则。Rectangle类具有两个职责,如果其中一个改变,会影响到两个应用程序的变化。

一个好的设计是把两个职责分离出来放在两个不同的类中,这样任何一个变化都不会影响到其他的应用程序。

  

开放封闭原则(OCP

 

所谓开放封闭原则就是软件实体应该对扩展开发,而对修改封闭。开放封闭原则是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化,降低耦合,而开放封闭原则正是对这一目标的最直接体现。

   开放封闭原则主要体现在两个方面:

   对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。

   对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

 

为什么要用到开放封闭原则呢?

软件需求总是变化的,世界上没有一个软件的是不变的,因此对软件设计人员来说,必须在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。

 

如何做到对扩展开放,对修改封闭呢?

     实现开放封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以对修改就是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。

     对于违反这一原则的类,必须通过重构来进行改善。常用于实现的设计模式主要有Template Method模式和Strategy模式。而封装变化,是实现这一原则的重要手段,将经常变化的状态封装为一个类。

以银行业务员为例

没有实现OCP的设计:

public classBankProcess

   {  //存款 

      public void Deposite(){}

       //取款

       public void Withdraw(){ }

       //转账

       public void Transfer(){}

    }

   public class BankStaff

    {

       private BankProcess bankpro = new BankProcess();

       public void BankHandle(Client client)

       {

           switch (client.Type)

           {  //存款

               case "deposite":

                   bankpro.Deposite();

                   break;

                   //取款

               case "withdraw":

                   bankpro.Withdraw();

                   break;

                   //转账

               case "transfer":

                   bankpro.Transfer();

                   break;

           }

       }

    }

    这种设计显然是存在问题的,目前设计中就只有存款,取款和转账三个功能,将来如果业务增加了,比如增加申购基金功能,理财功能等,就必须要修改BankProcess业务类。我们分析上述设计就不能发现把不能业务封装在一个类里面,违反单一职责原则,而有新的需求发生,必须修改现有代码则违反了开放封闭原则。

     从开放封闭的角度来分析,在银行系统中最可能扩展的就是业务功能的增加或变更。对业务流程应该作为扩展的部分来实现。当有新的功能时,不需要再对现有业务进行重新梳理,然后再对系统做大的修改。

如何才能实现耦合度和灵活性兼得呢?

那就是抽象,将业务功能抽象为接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。

以下是符合OCP的设计:

首先声明一个业务处理接口

public interface IBankProcess{  void Process();}

public classDepositProcess : IBankProcess

    {

       public void Process()

       { //办理存款业务

           Console.WriteLine("Process Deposit");

       }

}

public classWithDrawProcess : IBankProcess

    {

       public void Process()

       { //办理取款业务

           Console.WriteLine("Process WithDraw");

       }

}

public classTransferProcess : IBankProcess

    {

       public void Process()

       { //办理转账业务

           Console.WriteLine("Process Transfer");

       }

    }

public classBankStaff

    {

       private IBankProcess bankpro = null;

       public void BankHandle(Client client)

       {

           switch (client.Type)

           {   //存款

               case "Deposit":

                   userProc = new DepositUser();

                   break;

                   //转账

               case "Transfer":

                   userProc = new TransferUser();

                   break;

                   //取款

               case "WithDraw":

                   userProc = new WithDrawUser();

                   break;

           }

           userProc.Process();

       }

    }

这样当业务变更时,只需要修改对应的业务实现类就可以,其他不相干的业务就不必修改。当业务增加,只需要增加业务的实现就可以了。

 

设计建议:

开放封闭原则,是最为重要的设计原则,Liskov替换原则和合成/聚合复用原则为开放封闭原则提供保证。

可以通过TemplateMethod模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。

封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,例如银行业务中IBankProcess接口。

拒绝滥用抽象,只将经常变化的部分进行抽象。

 

里氏替换原则(LSP

 

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。包含以下4层含义:

1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

2.子类中可以增加自己特有的方法。

3.当子类的方法重载父类的方法时,子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松。

4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

     在项目中,采用里氏替换原则时,尽量避免子类的个性,一旦子类有个性,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的个性被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。

 

接口分离原则(ISP

 

不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

客户模块不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性。如Java中一个类实现多个接口,不同的接口给不用的客户模块使用,而不是提供给客户模块一个大的接口。

 

依赖倒置原则(DIP

 

所谓依赖倒置原则(DependenceInversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

     面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变化时,上层也要跟着变化,这就会导致模块的复用性降低而且大大提高了开发的成本。

面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序域实现细节的耦合度。

     比如一个合资汽车公司现在要求开发一个自动驾驶系统,只要汽车上安装上这个系统,就可以实现无人驾驶,该系统可以在福特车系列和本田车系列上使用。面向过程的结构图:

                                                              

实现代码如下:

public class HondaCar

    {

       public void Run() { Console.WriteLine("本田车启动了!"); }

       public void Turn() { Console.WriteLine("本田车拐弯了!"); }

       public void Stop() { Console.WriteLine("本田车停止了!"); }

    }

   public class FordCar

    {

       public void Run() { Console.WriteLine("福特车启动了!"); }

       public void Turn() { Console.WriteLine("福特车拐弯了!"); }

       public void Stop() { Console.WriteLine("福特车停止了!"); }

    }

   public class AutoSystem

    {

       public enum CarType{ Ford,Fonda}

       private HondaCar hondcar=new HondaCar();

       private FordCar fordcar=new FordCar();

       private CarType type;

       public AutoSystem(CarType carType)

       {

           this.type = carType;

       }

       public void RunCar()

       {

           if (this.type == CarType.Fonda)

           {

               hondcar.Run();

           }

           else if (this.type == CarType.Ford)

           {

               fordcar.Run();

           }

       }

       public void StopCar()

       {

           if (this.type == CarType.Fonda)

           {

               hondcar.Stop();

           }

           else if (this.type == CarType.Ford)

           {

               fordcar.Stop();

           }

       }

       public void TurnCar()

       {

           if (this.type == CarType.Fonda)

           {

               hondcar.Turn();

           }

           else if (this.type == CarType.Ford)

           {

               fordcar.Turn();

           }

       }

    }

显然这个实现代码也可满足现在的需求。

但是如何现在公司业务规模扩大了,该自动驾驶系统还要把吉普车也兼容了。这些就需要修改AutoSystem类如下:

public classAutoSystem

    {

       public enum CarType{ Ford,Fonda,Jeep}

       private HondaCar hondcar=new HondaCar();

       private FordCar fordcar=new FordCar();

       private Jeep jeep = new Jeep();

       private CarType type;

       public AutoSystem(CarType carType)

       {

           this.type = carType;

       }

       public void RunCar()

       {

           if (this.type == CarType.Fonda)

           {

               hondcar.Run();

           }

           else if (this.type == CarType.Ford)

           {

               fordcar.Run();

           }

           else if (this.type == CarType.Jeep)

           {

               jeep.Run();

           }

       }

       public void StopCar()

       {

           if (this.type == CarType.Fonda)

           {

               hondcar.Stop();

           }

           else if (this.type == CarType.Ford)

           {

               fordcar.Stop();

           }

           else if (this.type == CarType.Jeep)

           {

               jeep.Stop();

           }

       }

       public void TurnCar()

       {

           if (this.type == CarType.Fonda)

           {

               hondcar.Turn();

           }

           else if (this.type == CarType.Ford)

           {

               fordcar.Turn();

           }

           else if (this.type == CarType.Jeep)

           {

               jeep.Turn();

           }

       }

    }

       通过代码分析得知,上述代码也确实满足了需求,但是软件是不断变化的,软件的需求也是变化的,如果将来业务又扩大了,该自动驾驶系统还有能实现通用、三菱、大众汽车,这样我们不得不又要修改AutoSystem类了。这样会导致系统越来越臃肿,越来越大,而且依赖越来越多低层模块,只有低层模块变动,AutoSystem类就不得不跟着变动,导致系统设计变得非常脆弱和僵硬。

      导致上面所述问题一个原因是,含有高层策略的模块,如AutoSystem模块,依赖于它所控制的低层的具体细节的模块(如FordCarHondaCar)。如果能使AutoSystem模块独立于它所控制的具体细节,而是依赖抽象,那么我们就可以服用它了。这就是面向对象中的依赖倒置机制。如下类图:

                                                                        

 

实现代码如下:

public interface ICar

    {

       void Run();

       void Stop();

       void Turn();

    }

   public class HondaCar:ICar

    {

       public void Run() { Console.WriteLine("本田车启动了!"); }

       public void Turn() { Console.WriteLine("本田车拐弯了!"); }

       public void Stop() { Console.WriteLine("本田车停止了!"); }

    }

   public class FordCar :ICar

    {

       public void Run() { Console.WriteLine("福特车启动了!"); }

       public void Turn() { Console.WriteLine("福特车拐弯了!"); }

       public void Stop() { Console.WriteLine("福特车停止了!"); }

    }

   public class Jeep:ICar

    {

       public void Run() { Console.WriteLine("福特车启动了!"); }

       public void Turn() { Console.WriteLine("福特车拐弯了!"); }

       public void Stop() { Console.WriteLine("福特车停止了!"); }

    }

   public class AutoSystem

    {

       private ICar car;

       public AutoSystem(ICar car)

       {

           this.car = car;

       }

       public void RunCar()

       {

           this.car.Run();

       }

       public void StopCar()

       {

           this.car.Stop();

       }

       public void TurnCar()

       {

           this.car.Turn();

       }

    }

    现在Autosystem系统依赖于ICar这个抽象,而与具体的实现细节HondaCar:FordCar无关,所以实现细节的变化不会影响AutoSystem.对于实现细节只要实现ICar即可。即实现细节依赖于ICar抽象。

    综上所述:一个应用中的重要策略决定及业务正是在这些高层的模块中。也正是这些模块包含这应用的特性。但是,当这些模块依赖于低层模块时,低层模块的修改比较将直接影响到他们,迫使它们也改变。这种情况是荒谬的。

    应该是处于高层的模块去迫使那些低层的模块发生改变。处于高层的模块应优先于低层的模块。无论如何高层模块也不应该依赖于低层模块。而且我们想能够复用的是高层的模块,只有高层模块独立于低层模块时,复用才有可能。

     总之,高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。抽象不应该依赖于具体,具体应该依赖于抽象。

 

 

0 0
原创粉丝点击