• 设计模式之设计原则(中)


      接口隔离原则(Interface Segregation Principle ),简称ISP:该原则核心思想就是客户端不应该被强迫实现一些不会使用的接口,应该把胖接口中的方法分组,然后用多个接口来代替,每一个接口只服务与一个子模块。这个跟上次分享的单一职责原则类似。

    设计接口隔离原则的目的:当我们设计应用程序时,如果一个模块包含多个子模块,那我们应该正对该模块抽象。设想该模块由一个类实现,我们可以把系统抽象成一个接口,但是我们想要添加新的模块扩展程序时,如果要添加的模块只包含原来系统的一些子模块,那么就强迫我们实现该接口的所有方法,这样的接口被称之为胖接口或者被污染的接口。

      我们可以把这定义概括为一句话就是建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。看到这里大家有可能要疑惑了,这与单一职责原则不是相同的吗?错,接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”,专门的接口指什么?就是指提供给每个模块都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。

    接口隔离原则是对接口进行规范约束,其包含以下四层含义:

    1)接口尽量要小。根据接口隔离原则拆分接口时,必须首先满足单一职责原则。

    2)接口要高内聚

    3)定制服务

    4)接口设计是有限度的

    跟上一个原则一样我们也在举个简单的例子说明问题:

    public interface IWorker
        {
            void Work();
            void Eat();
        }
        public class Worker : IWorker
        {
            public void Work()
            {
                Console.WriteLine("要工作");
            }
    
            public void Eat()
            {
               Console.WriteLine("陪客户吃饭");
            }
        }

    类图如下:

      从上面代码我们可以假设,工人又要工作,又要陪客户吃饭,但是领导呢就不需要基层工作,平时陪客户吃吃饭就可以,这样的话比如有一个领导类实现该接口必须工作和吃饭类都要实现,这样对于领导类的话就多余了,所以我们改造一下上面的接口:

     1 public interface IWorkable
     2     {
     3         void Work();
     4     }
     5 
     6     public interface IFeeadable
     7     {
     8         void Eat();
     9     }
    10     //工人又工作,又要陪客户吃饭
    11     public class Workman : IWorker, IFeeadable
    12     {
    13         public void Work()
    14         {
    15             Console.WriteLine("要工作");
    16         }
    17 
    18         public void Eat()
    19         {
    20             Console.WriteLine("陪客户吃饭");
    21         }
    22     }
    23     //领导直陪客户吃饭
    24     public class Leader : IFeeadable
    25     {
    26         public void Eat()
    27         {
    28             Console.WriteLine("陪客户吃饭");
    29         }
    30     }

    类图如下:

    以上代码可以看出来这样写法有很大的灵活性,具体我就不在细说了。

    里氏替换原则(Liskov Substitution Principle ),简称LSP:在核心思想就是在一个软件系统中,子类可以替换任何其基类能出现的地方,并且替换之后,代码还能正常工作。

    就是说当我们在设计程序模块时,我们会创建类层次结构,然后我们通过扩展一些类来创建他们的子类,我们必须确保基类的引用被子类替换而不影响程序功能,否则我们在已有的程序模块中使用他们时将会产生不可预料的结果。

    我们来看看一段代码:

     1 public class Father
     2     {
     3         public string type;
     4         public Father()
     5         {
     6             type = "父类";
     7         }
     8 
     9         public void Method()
    10         {
    11             Console.WriteLine("我是父类方法");
    12         }
    13     }
    14 
    15     public class Sun : Father
    16     {
    17         public Sun()
    18         {
    19             type = "子类";
    20         }
    21 
    22         public void MyMethod()
    23         {
    24             Console.WriteLine("我是子类方法");
    25         }
    26     }

    类图如下:

    客户端调用代码如下:

     1 public static void DoSomthing(Father father)
     2         {
     3             switch (father.type)
     4             {
     5                 case"父类":
     6                     father.Method();
     7                     break;
     8                 case "子类":
     9                     ((Sun)father).MyMethod();
    10                     break;
    11             }
    12         }

    我们从客户端调用方法可以看出,调用方法根据传入的类型判断,这样的缺点就是如果要有100子类,那么该方法就会写100个判断,更多的话就会进入无限判断,这样对于代码的阅读行和维护为极差,那我们把该方法改造一下:

     1 public class Father
     2     {
     3         public string type;
     4         public Father()
     5         {
     6             type = "父类";
     7         }
     8 
     9         public virtual void Method()
    10         {
    11             Console.WriteLine("我是父类");
    12         }
    13     }
    14 
    15     public class Sun : Father
    16     {
    17         public Sun()
    18         {
    19             type = "子类";
    20         }
    21         public override void Method()
    22         {
    23             Console.WriteLine("我是子类");
    24         }
    25     }

    客户端调用如下:

    1   public static void DoSomthing(Father f)
    2         {
    3             f.Method();
    4         }
    5         public static void DoSomthing(Sun f)
    6         {
    7             f.Method();
    8         }

    从以上代码可以看出,免去客户端根据判断来调用方法,并且子类完全替换父类方法,这就是传说中的里氏替换原则。

  • 相关阅读:
    洛谷 P2008 大朋友的数字
    [USACO10FEB]慢下来Slowing down
    HAOI2007 理想的正方形 单调队列
    滑动窗口
    双栈排序
    概率无向图模型与条件随机场的异同
    P-R曲线出现凹陷的原因
    MaskLab-实例分割(使用语义分割和方向特征精细化目标检测)
    模拟递归生成器
    递归生成器
  • 原文地址:https://www.cnblogs.com/yaosutu/p/3891240.html
Copyright © 2020-2023  润新知