65.9K
CodeProject 正在变化。 阅读更多。
Home

理解单一职责原则和接口隔离原则

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (22投票s)

2013年5月3日

CPOL

4分钟阅读

viewsIcon

35844

本文将帮助您更好地理解 SRP(单一职责原则)和 ISP(接口隔离原则)

引言

在此,我将讨论 SOLID 中的单一职责和接口隔离原则。那么 SOLID 是什么意思呢?SOLID 是面向对象设计原则,其中每个字母都有其自身的含义。

  • S-> 单一职责
  • O-> 开闭原则
  • L-> 里氏替换原则   
  • I-> 接口隔离原则    
  • D-> 依赖倒置原则

根据 维基百科,SOLID 的定义是:

"SOLID 是在处理软件时可以应用的一组指导原则,通过使程序员重构软件的源代码,直到其既可读又可扩展,从而消除代码坏味。"

背景 

如果您阅读了我之前的文章,将会非常有帮助,因为我将在这里使用相同的示例。所以请先阅读下面的文章。

理解开闭原则和依赖倒置原则 .  

使用代码

在开始技术讨论之前,我想回答以下问题:

  • 什么是单一职责原则?
  • 答案:" 每个类都应该只有一个职责, "
  • 什么是接口隔离?
  • 答案: "不应强迫客户端依赖于它们不使用的接口方法"

我将使用我在上一篇文章中使用的示例。让我们考虑一个例子来更好地理解。假设我需要电脑的价格。我去了电脑商店,询问笔记本电脑的价格。让我们将我的需求转化为代码。

public interface IComputerDescription
{
    string GetDescription();
    string GetColor();
    double GetPrice();
    double CalculatePriceAfterTax();
    
}
 
public class Desktop : IComputerDescription
{
    double desktopPrice = 110;     
    public string GetDescription()
    {
        return " You get a Desktop";
    }     
    public string GetColor()
    {
        return " Color is White";
    }
    public double GetPrice()
    {
        return desktopPrice;
    }
    public double CalculatePriceAfterTax()
    {
        return desktopPrice + desktopPrice * .20;
    }
}
public class Laptop : IComputerDescription
{
    double laptopPrice = 200;   
    public string GetDescription()
    {
        return " You get a Laptop";
    }        
    public string GetColor()
    {
        return " Color is Black and Red";
    }
    public double GetPrice()
    {
       return laptopPrice;
    }
    public double CalculatePriceAfterTax()
    {
        return laptopPrice + laptopPrice * .20;
    }
}
public class Tablet : IComputerDescription
{
    double tabletPrice = 500;    
    public string GetDescription()
    {
        return " You get a Tablet";
    }   
    public string GetColor()
    {
        return " Color is Silver";
    }
    public double GetPrice()
    {
        return tabletPrice;
    }
    public double CalculatePriceAfterTax()
    {
        return tabletPrice + tabletPrice * .20;
    }
}
 
public class GiftItem : IComputerDescription
{
    public string GetDescription()
    {
        return "Yes you get one PenDrive as gift item enjoy";
    }
    public string GetColor()
    {
        return "Choose Any color from Red Black and White";
    }
    public double GetPrice()
    {
        return 0;
    }
 
    public double CalculatePriceAfterTax()
    {
        return 0;
    }
}
 
public class Shop
{
    public string GetMyComputer(IComputerDescription cmptype)
    {
        // No matter how many types of computer comes 
        var myComp = cmptype.GetDescription(); 
        return myComp;
    }
    public string GetMyComputerPrice(IComputerDescription cmptype)
    {        
        var myCompprice = "Price is : " + cmptype.CalculatePriceAfterTax().ToString();
        return myCompprice;
    }
    public string GetAvailableColor(IComputerDescription cmptype)
    {
        var myCompcolor =  cmptype.GetColor().ToString();
        return myCompcolor;
    }
    public string WhatIsThecolorofGiftItem(IComputerDescription cmptype)
    {
        return GetAvailableColor(cmptype);        
    }
    public string IsThereAnyGiftItem(IComputerDescription gftType)
    {
        return GetMyComputer(gftType);
    }
}

所以我问的是笔记本电脑的价格,对吧?

var computerShop = new Shop();
computerShop.GetMyComputerPrice(new Laptop());

如果您运行代码,它将正常执行并给出输出"价格是:240"

笔记本电脑有哪些可用颜色?答案是:"颜色是黑色和红色"  

computerShop.GetAvailableColor(new Laptop());

然后我再次问,如果我买一台笔记本电脑,我会得到任何礼物或免费物品吗?再次将人类语言转化为代码  

computerShop.IsThereAnyGiftItem(new GiftItem());

这将为您提供输出"是的,您会收到一个 U 盘作为礼品,请享用"

正常的行为是我们得到任何礼物都会感到兴奋。我也变得非常兴奋,并带着兴奋提出了另一个问题。您的礼品 U 盘有哪些可用颜色?答案是:"可从红色、黑色和白色中任选一种颜色"。  

computerShop.WhatAreTheColorofGiftItem(new GiftItem());

我决定购买一台黑色笔记本电脑,并想要一个白色 U 盘。请注意代码,等等,等等(我想到了)!!(我开始思考)为什么,发生了什么,出了什么问题?分析代码后,我得到了答案。购买笔记本电脑和获得 U 盘没有任何问题。但我们的代码违反了 SRP(单一职责原则)和 ISP(接口隔离原则)的规则  

你注意到吗?还没有,仍然感到困惑? 好的,没问题,我会解释。   

我们是如何违反 SRP 规则的?

"一个类永远不应该有超过一个改变的理由。" — Robert Martin,SRP 论文,链接自 The Principles of OOD "

看看下面的函数,它们是罪魁祸首,这是价格计算逻辑,但它在 desktop laptoptablet 类中。所以,如果价格发生变化,我们就需要更改这些类,如果计算逻辑发生变化,我们就需要再次执行。因此,我们需要出于两个不同的原因更改同一个类,因为类有多个职责(这是违规)。 

public double CalculatePriceAfterTax()
{
   return desktopPrice+ (desktopPrice *0.2);
}
public double CalculatePriceAfterTax()
{
    return laptopPrice + (laptopPrice * 0.2);
}
public double CalculatePriceAfterTax()
{
   return tabletPrice+(tabletPrice*.20);
}

现在看看 Desktop 类是如何违反 SRP 的 

public class Desktop : IComputerDescription
{
    double desktopPrice = 130;     // Change Price (one reason)
    public string GetDescription()
    {
        return " You get a Desktop";
    }     
    public string GetColor()
    {
        return " Color is White";
    }
    public double GetPrice()
    {
        return desktopPrice;
    }
    public double CalculatePriceAfterTax()
    // Change calculation Logic (another different reason )
    {
        return desktopPrice + 
           (desktopPrice * .20)+1; // Violation of SRP
    }
}

我们是如何违反 ISP 规则的?  

"客户端不应依赖于他们不使用的接口。" — Robert Martin,ISP 论文,链接自 The Principles of OOD 

是的,我们通过将接口实现到 GiftItem 来做到了这一点。因为礼品是免费的,不需要计算价格,但我们强制实现了它。(违反 ISP)。

public class GiftItem : IComputerDescription
{
    public string GetDescription()
    {
        return "Yes you get one PenDrive as gift item enjoy";
    }
    public string GetColor()
    {
        return " Any color from Red Black and White";
    }
    public double GetPrice()
    {
        return 0; // No Use here but forcefully implemented ------ Violation of ISP
    }
 
    public double CalculatePriceAfterTax()
    {
        return 0; // No Use here but forcefully implemented  ------ Violation of ISP
    }
}

你注意到违规了吗?是的,我们因为两个不同的原因修改了类,并毫无必要地在 GiftItem 类中实现了接口方法 CalculatePriceAfterTaxGetPrice 。现在,让我们遵循 SRP 和 ISP 规则并实现一个新的结构。如果我们能将计算价格的职责委托给另一个类,并从 GiftItem 类中移除未使用的实现,那么我们就可以实现我们的目标。 

看看下面的代码,我们是如何分解接口的(这有助于轻松实现 ISP 规则)

public interface IComputerDescription
{
    string GetDescription();
    string GetColor();
}
public interface IComputerPrice
{
    double GetPrice();
}
public interface IPriceCalculation
{
    double CalculatePriceAfterTax(IComputerPrice c);
}

那么实现也将改变,对吧?  

public class Desktop : IComputerDescription,IComputerPrice
{
    double desktopPrice = 130;     
    public string GetDescription()
    {
        return " You get a Desktop";
    }     
    public string GetColor()
    {
        return " Color is White";
    }
    public double GetPrice()
    {
        return desktopPrice;
    }    
}
 
public class Laptop : IComputerDescription,IComputerPrice
{
    double laptopPrice = 200;   
    public string GetDescription()
    {
        return " You get a Laptop";
    }        
    public string GetColor()
    {
        return " Color is Black";
    }
    public double GetPrice()
    {
       return laptopPrice;
    }   
}
 
public class Tablet : IComputerDescription,IComputerPrice
{
    double tabletPrice = 500;    
    public string GetDescription()
    {
        return " You get a Tablet";
    }   
    public string GetColor()
    {
        return " Color is Silver";
    }
    public double GetPrice()
    {
        return tabletPrice;
    }    
}
public class GiftItem : IComputerDescription
{
    public string GetDescription()
    {
        return "Yes you get one PenDrive as gift item enjoy";
    }
    public string GetColor()
    {
        return " Any color from Red Black and White";
    }    
}
//New Class introduce to take the responsibility of calculation price
public class CalculateComputerPrice:IPriceCalculation
{
    public double CalculatePriceAfterTax(IComputerPrice c)
    {
        return c.GetPrice() + c.GetPrice() * .20;
    }
}
 
public class Shop
{
    public string GetMyComputer(IComputerDescription cmptype)
    {
        // No matter how many types of computer comes 
        var myComp = cmptype.GetDescription(); 
        return myComp;
    }
    public string GetMyComputerPrice(IPriceCalculation cmpCal, IComputerPrice cmpPrice)
    {
        var myCompprice = "Price is : " + 
                 cmpCal.CalculatePriceAfterTax(cmpPrice).ToString();
        return myCompprice;
    }
    public string GetAvailableColor(IComputerDescription cmptype)
    {
        var myCompcolor =  cmptype.GetColor().ToString();
        return myCompcolor;
    }
    public string WhatIsThecolorofGiftItem(IComputerDescription cmptype)
    {
        return GetAvailableColor(cmptype);        
    }
    public string IsThereAnyGiftItem(IComputerDescription gftType)
    {
        return GetMyComputer(gftType);
    }
}

值得关注的点  

所以,如果我再次用我们新的代码结构问同样的问题,答案会是什么?

问题 1) 笔记本电脑的价格是多少? 

答案: "价格是:240"。 与之前相同,但由不同的类(CalculateComputerPrice)负责计算。所以它满足(SRP) 

var queryforProduct = new Laptop();
var computerShop = new Shop();
var responsibleforCalculation = new CalculateComputerPrice();
var myAnswer = computerShop.GetMyComputerPrice(responsibleforCalculation, queryforProduct);

问题 2) 笔记本电脑有哪些可用颜色? 

答案:"颜色是黑色和红色"  与之前相同。因此,代码不受重构的影响。

var myAnswer = computerShop.GetAvailableColor(queryforProduct);

问题 3) 如果我买一台笔记本电脑,我会得到任何礼物或免费物品吗?

答案:"是的,您会收到一个 U 盘作为礼品,请享用" 答案相同,但 GiftItem 类移除了未使用的项目。所以它满足(ISP)

public class GiftItem : IComputerDescription
{
    public string GetDescription()
    {
        return "Yes you get one PenDrive as gift item enjoy";
    }
    public string GetColor()
    {
        return "Choose Any color from Red Black and White";
    }    
}
var queryforProduct = new GiftItem();
var computerShop = new Shop();
var myAnswer = computerShop.IsThereAnyGiftItem(queryforProduct);

问题 4) 您的礼品 U 盘有哪些可用颜色?

答案:"可从红色、黑色和白色中任选一种颜色"。   答案相同,但 GiftItem 类移除了未使用的项目。所以它满足(ISP) 

var queryforProduct = new GiftItem();
var computerShop = new Shop();        
var myAnswer = computerShop.WhatIsThecolorofGiftItem(queryforProduct); 

所以现在我们的代码同时满足 SRP 和 ISP。 

© . All rights reserved.