SOLID, GRASP和其他面向对象设计的基本原则(二)

AIM TO

学习面向对象的设计原则,并牢牢掌握SOLID和GRASP规则背后的思想

 

原文 By Muhammad Umair  ·  Feb. 13, 17 · Web Dev Zone

 

 

04

Interface Segregation Principle(接口隔离原则)

接口隔离原则(ISP)描述为:

客户端不应该被强迫依赖他们不使用的接口

还是考虑前一个例子:

Public Interface IDevice{
        Void Open();
        Void Read();
        Void Close();
    }

这个接口有三个实现类,USB设备、网络设备和PCID设备。这个接口对于网络和PCID设备来说够用,但是USB设备还需要另一个函数(Refresh())才能运行正常。

 

和USB设备一样,也许还有另外的设备也需要这个函数来支持工作。为此,接口被更新如下:

 Public Interface IDevice{
        Void Open();
        Void Refresh();
        Void Read();
        Void Close();
    }

那么问题来了,任何一个实现该接口的类都需要去实现Refresh函数。

 

例如为了满足以上的设计,必须对网络设备和PCID设备添加下面的代码:

public  void Refresh(){
        // Yes nothing here… just a useless blank function
    }

因此,这个设备基类被视为一个胖接口(过多的函数)。这种设计违反了接口隔离原则,因为它造成了客户端不必要的依赖。

 

有很多方法可以解决这个问题,不过我将在预定义的面向对象的解决方案范畴内处理这个问题。

 

我们知道在open操作之后就会直接调用refresh函数,因此,我改变逻辑将设备客户端的refresh函数迁移至具体的实现类内部。在本例中我将调用refresh的逻辑移动到USB设备的具体实现类中:

Public Interface IDevice{
        Void Open();
        Void Read();
        Void Close();
    }
    Public class USBDevice:IDevice{
        Public void Open{
            // open the device here…
            // refresh the device
            this.Refresh();
         }
        Private void Refresh(){
            // make the USb Device Refresh
         }
    }

通过这个方式,我减少了基类中的函数数目,让它变得更轻了。

05

Dependency Inversion Principle(依赖反转)

这条原则是对上面所讨论的原则的一个概述。

 

在我们给出DIP的书面定义之前,请让我介绍一个与此紧密相连的一条原则,以帮助我们理解DIP。

 

这条原则是:面向接口编程,而不是面向实现编程

考虑下面这个简单的例子:

Class PCIDevice{
    Void open(){}
    Void close(){}
}
Static void Main(){
    PCIDevice aDevice = new PCIDevice();
    aDevice.open();
    //do some work
    aDevice.close();
}

上面的示例代码违背了面向接口编程,因为我们正在操作的引用是具体的类对象PCIDevice,下面的代码则遵循了这个原则:

Interface IDevice{
    Void open();
    Void close();
}
Class PCIDevice implements IDevice{
    Void open(){ // PCI device opening code }
    Void close(){ // PCI Device closing code }
}
Static void Main(){
    IDevice aDevice = new PCIDevice();
    aDevice.open();
    //do some work
    aDevice.close();
}

所以这条原则很容易去遵循。依赖反转与此类似,但需要我们做的更多。

 

依赖反转:高级模块不应该依赖低级模块。二者应该依赖于抽象。

 

你可以把"两者都应该依赖于抽象"简单的理解成面向接口编程。那么什么是高级模块和低级模块?

 

为了这条原则的第一部分,我们需要理解高级模块和低级模块实际上是什么。看如下代码:

Class TransferManager{
public void TransferData(USBExternalDevice usbExternalDeviceObj,SSDDrive  ssdDriveObj){
            Byte[] dataBytes = usbExternalDeviceObj.readData();
           // work on dataBytes e.g compress, encrypt etc..
            ssdDriveObj.WrtieData(dataBytes);
        }
}
Class USBExternalDevice{
Public byte[] readData(){
        }
}
Class SSDDrive{
Public void WriteData(byte[] data){
}
}

上面的代码有三个类,TransferManager代表高级模块,因为它在一个方法中用了其它两个类。因此其他两个类则是低级模块。

 

上面的代码中,高级模块是直接使用低级模块的(没有任何抽象),因此违背了依赖反转原则。

 

违背了依赖反转这条原则会让你的软件系统变得难以更改。比如,如果你想增加其他的外部设备,你将不得不

改变高级模块。因此你的高级模块将会依赖于低级模块,依赖会让代码变得难以改变。

 

如果你理解了上面的原则"面向接口编程",那么就很容易解决这个问题:

Class USBExternalDevice implements IExternalDevice{
Public byte[] readData(){
}
}
Class SSDDrive implements IInternalDevice{
Public void WriteData(byte[] data){
}
}
Class TransferManager implements ITransferManager{
public void Transfer(IExternalDevice externalDeviceObj, IInternalDevice internalDeviceObj){
           Byte[] dataBytes = externalDeviceObj.readData();
           // work on dataBytes e.g compress, encrypt etc..
           internalDeviceObj.WrtieData(dataBytes);
        }
}
Interface IExternalDevice{
        Public byte[] readData();
}
Interfce IInternalDevice{
Public void WriteData(byte[] data);
}
Interface ITransferManager {
public void Transfer(IExternalDevice usbExternalDeviceObj,SSDDrive  IInternalDevice);
}

在上面的代码中,高级模块和低级模块都依赖于抽象,符合了依赖反转原则。

06

Hollywood(好莱坞原则)

这条原则和依赖反转原则类似:不要调用我们,我们会给你

 

这意味着高级组件可以以一种互不依赖的方式去支配低级组件。

 

这条原则可以防止依赖恶化。依赖恶化发生在每个组件都依赖于其他各个组件。换句话说,依赖恶化是让依赖发生在各个方向(向上,横向,向下)。

Hollywood原则可以让我们时依赖只向一个方向。

 

DIP和Hollywood之间的差异给了我们一条通用原则:无论是高级组件还是低级组件,都要依赖于抽象而不是具体的类。另一方面,Hollywood原则

强调了高级组件和低级组件应该以不产生依赖的方式交互。

07

 Polymorphism(多态)

什么?多态也是设计原则?没错,多态是任何面向对象语言都要提供的基础特征,它可以让父类的引用指向子类。

 

它同时也是GRASP的设计原则之一。这条原则为你在面向对象设计中提供了知道方针。

 

这条原则严格限制了运行时类型信息的使用(RTTI)。在C#中,我们用如下方式实现RTTI:

if(aDevice.GetType() == typeof(USBDevice)){
//This type is of USBDEvice
}

在java中,RTTI由getClass()或者instanceOf()完成:

if(aDevice.getClass() == USBDevice.class){
    // Implement USBDevice
     Byte[] data = USBDeviceObj.ReadUART32();
}

如果你曾在你的项目中编写了类似的代码,那么现在是时候重构你的代码以符合多态原则,看如下例图:

在此我在接口中生成了read方法,然后委托他们的实现类去实现该方法,现在,我只用方法Read:

//RefactoreCode
IDevice aDevice = dm.getDeviceObject();
aDevice.Read();

getDeviceObject()的实现来自哪里?这是我们即将要讨论的创造者原则和信息专家原则。

下期预告

1. Information Expert(信息专家原则)

2. Creator(创造者原则)

3. Pure Fabrication(纯虚构模式)

4. Controller(控制器原则)

5. Favor Composition Over Inheritance(组合优于继承原则)

6. Indirection(间接原则)

 

posted @ 2017-05-03 19:17  傳奇  阅读(301)  评论(0)    收藏  举报