精华内容
下载资源
问答
  • 设计模式六大原则

    2018-03-06 10:06:57
    设计模式六大原则的一点总结,欢迎免费下载。 设计模式六大原则(1):单一职责原则 设计模式六大原则(2):里氏替换原则 设计模式六大原则(3):依赖倒置原则 设计模式六大原则(4):接口隔离原则 设计...
  • php 设计模式六大原则

    2017-10-20 22:19:10
    php 设计模式六大原则 单一职责原则 里氏替换原则 依赖倒置原则 接口隔离原则 迪米特法则 开闭原则 word版
  • 设计模式+六大原则pdf

    2019-04-23 21:40:42
    孙玉山主编的设计模式所有设计模式+体系结构题目案例源码
  • 设计模式六大原则.doc

    2020-05-06 22:41:28
    设计模式六大原则(1):单一职责原则 设计模式六大原则(2):里氏替换原则 设计模式六大原则(3):依赖倒置原则 设计模式六大原则(4):接口隔离原则 设计模式六大原则(5):迪米特法则 设计模式六大原则...
  • 设计模式六大原则
  • Unity3d程序必备设计模式六大原则

    千次阅读 2016-06-29 07:35:41
    Unity3d程序必备设计模式六大原则 Unity编程众所周知,它是属于脚本化,脚本没有一个具体的概念跟架构,导致在项目过程中,经常出现哪里需要实现什么功能,就随便添加脚本,结果,就造成了一片混乱,不好管理。更有...

    Unity3d程序必备设计模式六大原则

    Unity编程众所周知,它是属于脚本化,脚本没有一个具体的概念跟架构,导致在项目过程中,经常出现哪里需要实现什么功能,就随便添加脚本,结果,就造成了一片混乱,不好管理。更有甚者,自己的写的代码闲置一段时间后,再去想找某个功能的实现,都要在视图中翻来覆去找半天。

    因此,一个好的设计模式是多么的重要啊,那么,我们在使用Unity3d开发东西的时候,脚本架构到底应该如何来写?
    其实,我也给不了你们具体答案,因为每个人的开发习惯,每个团队的开发模式也各有千秋,在此我只做几种设计模式的总结,主要参考书籍有《设计模式》《设计模式之禅》《大话设计模式》以及网上一些零散的文章,但主要内容还是我本人的一些经验以及感悟。写出来的目的一方面是系统地整理一下,一方面也与广大的网友分享。

    设计模式六大原则(1):单一职责原则

    说到单一职责原则,很多人都会不屑一顾。因为它太简单了,稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责被分化成了更细的职责。

    如:用一个类描述动物呼吸这个场景。
    [csharp]  view plain  copy
    1. class Animal  
    2. {  
    3.     public void breathe(string animal)  
    4.     {  
    5.                 Debug.Log(animal+"呼吸空气");  
    6.         }  
    7. }  
    8. public class Client  
    9. {  
    10.     Animal animal = new Animal();  
    11.     void Start()  
    12.     {  
    13.       animal.breathe("牛");  
    14.       animal.breathe("羊");  
    15.       animal.breathe("猪");  
    16.     }  
    17. }  

    运行结果:
    牛呼吸空气
    羊呼吸空气
    猪呼吸空气

    程序上线后,发现问题了,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。
    修改时如果遵循单一职责原则,需要将Animal类细分为陆生动物类Terrestrial,水生动物Aquatic,代码如下:

    [csharp]  view plain  copy
    1. class Terrestrial  
    2.     {  
    3.         public void breathe(String animal){  
    4.             Debug.Log(animal + "呼吸空气");  
    5.         }  
    6.     }  
    7.     class Aquatic  
    8.     {  
    9.         public void breathe(String animal){  
    10.             Debug.Log(animal + "呼吸水");  
    11.         }  
    12.     }  
    13.    
    14.     public class Client  
    15.     {  
    16.         public static void main(String[] args)  
    17.         {  
    18.             Terrestrial terrestrial = new Terrestrial();  
    19.             Debug.Log(terrestrial.breathe("牛"));  
    20.            Debug.Log(terrestrial.breathe("羊"));  
    21.            Debug.Log(terrestrial.breathe("猪"));  
    22.    
    23.             Aquatic aquatic = new Aquatic();  
    24.            Debug.Log( aquatic.breathe("鱼"));  
    25.         }  
    26.     }  

    运行结果:
    牛呼吸空气
    羊呼吸空气
    猪呼吸空气
    鱼呼吸水

    我们会发现如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。
    而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:

    [csharp]  view plain  copy
    1. class Animal  
    2.    {  
    3.        public void breathe(String animal)  
    4.        {  
    5.                    if("鱼".equals(animal))  
    6.            {  
    7.                           Debug.Log((animal+"呼吸水"));  
    8.                    }  
    9.            else  
    10.            {  
    11.                            Debug.Log((animal+"呼吸空气"));  
    12.                    }  
    13.            }  
    14.    }  
    15.    
    16.    public class Client  
    17.    {  
    18.        public static void main(String[] args)  
    19.        {  
    20.            Animal animal = new Animal();  
    21.            Debug.Log(animal.breathe("牛"));  
    22.            Debug.Log(animal.breathe("羊"));  
    23.            Debug.Log(animal.breathe("猪"));  
    24.            Debug.Log(animal.breathe("鱼"));  
    25.        }  
    26.    }  

    可以看到,这种修改方式要简单的多。
    但是却存在着隐患:有一天需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用“猪”“牛”“羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛呼吸水”了。这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。

    [csharp]  view plain  copy
    1. class Animal  
    2.     {  
    3.         public void breathe(String animal){  
    4.                 Debug.Log(animal+"呼吸空气");  
    5.         }  
    6.    
    7.         public void breathe2(String animal){  
    8.                 Debug.Log(animal+"呼吸水");  
    9.         }  
    10.     }  
    11.    
    12.     public class Client  
    13.     {  
    14.         public static void main(String[] args)  
    15.         {  
    16.             Animal animal = new Animal();  
    17.             Debug.Log(animal.breathe("牛"));  
    18.            Debug.Log(animal.breathe("羊"));  
    19.            Debug.Log(animal.breathe("猪"));  
    20.            Debug.Log(animal.breathe2("鱼"));  
    21.         }  
    22.     }  

    可以看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,需要根据实际情况来确定。
    我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则。

    遵循单一职责原的优点有:
    可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

    设计模式六大原则(2):里氏替换原则

    肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑。其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。简单来说的话,就是当我们使用继承时,遵循里氏替换原则。
    注:类B继承类A时,除添加新的方法完成新增功外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

    继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

    继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

    [csharp]  view plain  copy
    1. class A  
    2.     {  
    3.         public int func1(int a, int b)  
    4.         {  
    5.             return a - b;  
    6.         }  
    7.     }  
    8.    
    9.     public class Client  
    10.     {  
    11.        void Start()  
    12.        {  
    13.                     A a = new A();  
    14.                     Debug.Log(("100-50="+a.func1(100, 50));  
    15.                     Debug.Log(("100-80="+a.func1(100, 80)));  
    16.             }  
    17.     }  

    运行结果:
    100-50=50
    100-80=20

    后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。
    即类B需要完成两个功能:
    两数相减。
    两数相加,然后再加100。

    由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:

    [csharp]  view plain  copy
    1. class B:A{  
    2.         public int func1(int a, int b){  
    3.                 return a+b;  
    4.         }  
    5.            
    6.         public int func2(int a, int b){  
    7.                 return func1(a,b)+100;  
    8.         }  
    9. }  
    10.    
    11. public class Client{  
    12.          void Start()  
    13.      {  
    14.                 B b = new B();  
    15.                 Debug.Log("100-50="+b.func1(100, 50));  
    16.                 Debug.Log("100-80="+b.func1(100, 80));  
    17.                 Debug.Log("100+20+100="+b.func2(100, 20));  
    18.         }  

    类B完成后,运行结果:
    100-50=150
    100-80=180
    100+20+100=220

    我们发现原本运行正常的相减功能发生了错误。
    原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。
    在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。
    在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,
    但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。
    如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

    里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
    1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    2.子类中可以增加自己特有的方法。
    3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

    看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。
    所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?
    后果就是:你写的代码出问题的几率将会大大增加。

    设计模式六大原则(3):依赖倒置原则

    定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

    以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。
    抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

    依赖倒置原则的核心思想是面向接口编程,我们依旧用一个例子来说明面向接口编程比相对于面向实现编程好在什么地方。
    场景是这样的,母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。代码如下:

    [csharp]  view plain  copy
    1. class Book{  
    2.         public String getContent(){  
    3.                 return "很久很久以前有一个阿拉伯的故事……";  
    4.         }  
    5. }  
    6.    
    7. class Mother{  
    8.         public void narrate(Book book){  
    9.                 Debug.Log("妈妈开始讲故事");  
    10.                 Debug.Log(book.getContent());  
    11.         }  
    12. }  
    13.    
    14. public class Client{  
    15.         void Start()  
    16.     {  
    17.                 Mother mother = new Mother();  
    18.                 Debug.Log(mother.narrate(new Book()));  
    19.         }  
    20. }  

    运行结果:
    妈妈开始讲故事
    很久很久以前有一个阿拉伯的故事……

    运行良好,假如有一天,需求变成这样:不是给书而是给一份报纸,让这位母亲讲一下报纸上的故事,报纸的代码如下:
    [csharp]  view plain  copy
    1. class Newspaper{  
    2.         public String getContent(){  
    3.                 return "林书豪38+7领导尼克斯击败湖人……";  
    4.         }  
    5. }  

    这位母亲却办不到,因为她居然不会读报纸上的故事,这太荒唐了,只是将书换成报纸,居然必须要修改Mother才能读。
    假如以后需求换成杂志呢?换成网页呢?
    还要不断地修改Mother,这显然不是好的设计。
    原因就是Mother与Book之间的耦合性太高了,必须降低他们之间的耦合度才行。
    我们引入一个抽象的接口IReader。
    读物,只要是带字的都属于读物:

    [csharp]  view plain  copy
    1. interface IReader{  
    2.         public String getContent();  
    3. }  

    Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为:

    [csharp]  view plain  copy
    1. class Newspaper : IReader {  
    2.         public String getContent(){  
    3.                 return "林书豪17+9助尼克斯击败老鹰……";  
    4.         }  
    5. }  
    6. class Book : IReader{  
    7.         public String getContent(){  
    8.                 return "很久很久以前有一个阿拉伯的故事……";  
    9.         }  
    10. }  
    11.    
    12. class Mother{  
    13.         public void narrate(IReader reader){  
    14.                 Debug.Log("妈妈开始讲故事");  
    15.                 Debug.Log(reader.getContent());  
    16.         }  
    17. }  
    18.    
    19. public class Client{  
    20.         public static void main(String[] args){  
    21.                 Mother mother = new Mother();  
    22.                 Debug.Log(mother.narrate(new Book()));  
    23.                 Debug.Log(mother.narrate(new Newspaper()));  
    24.         }  
    25. }  

    运行结果:
    妈妈开始讲故事
    很久很久以前有一个阿拉伯的故事……
    妈妈开始讲故事
    林书豪17+9助尼克斯击败老鹰……

    这样修改后,无论以后怎样扩展Client类,都不需要再修改Mother类了。
    这只是一个简单的例子,实际情况中,代表高层模块的Mother类将负责完成主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大。
    所以遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。

    采用依赖倒置原则给多人并行开发带来了极大的便利,比如上例中,原本Mother类与Book类直接耦合时,Mother类必须等Book类编码完成后才可以进行编码,因为Mother类依赖于Book类。修改后的程序则可以同时开工,互不影响,因为Mother与Book类一点关系也没有。参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。现在很流行的TDD开发模式就是依赖倒置原则最成功的应用。

    在实际编程中,我们一般需要做到如下3点:
    1.低层模块尽量都要有抽象类或接口,或者两者都有。
    2.变量的声明类型尽量是抽象类或接口。使用继承时遵循里氏替换原则。
    3.依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。

    设计模式六大原则(4):接口隔离原则

    定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 
    将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
    举例来说明接口隔离原则:

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

    [csharp]  view plain  copy
    1. interface I {  
    2.         public void method1();  
    3.         public void method2();  
    4.         public void method3();  
    5.         public void method4();  
    6.         public void method5();  
    7. }  
    8.    
    9. class A{  
    10.         public void depend1(I i){  
    11.                 i.method1();  
    12.         }  
    13.         public void depend2(I i){  
    14.                 i.method2();  
    15.         }  
    16.         public void depend3(I i){  
    17.                 i.method3();  
    18.         }  
    19. }  
    20.    
    21. class B : I{  
    22.         public void method1() {  
    23.                 Debug.Log("类B实现接口I的方法1");  
    24.         }  
    25.         public void method2() {  
    26.                 Debug.Log("类B实现接口I的方法2");  
    27.         }  
    28.         public void method3() {  
    29.                 Debug.Log("类B实现接口I的方法3");  
    30.         }  
    31.         //对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,  
    32.         //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。  
    33.         public void method4() {}  
    34.         public void method5() {}  
    35. }  
    36.    
    37. class C{  
    38.         public void depend1(I i){  
    39.                 i.method1();  
    40.         }  
    41.         public void depend2(I i){  
    42.                 i.method4();  
    43.         }  
    44.         public void depend3(I i){  
    45.                 i.method5();  
    46.         }  
    47. }  
    48.    
    49. class D : I{  
    50.         public void method1() {  
    51.                 Debug.Log("类D实现接口I的方法1");  
    52.         }  
    53.         //对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,  
    54.         //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。  
    55.         public void method2() {}  
    56.         public void method3() {}  
    57.    
    58.         public void method4() {  
    59.                 Debug.Log("类D实现接口I的方法4");  
    60.         }  
    61.         public void method5() {  
    62.                 Debug.Log("类D实现接口I的方法5");  
    63.         }  
    64. }  
    65.    
    66. public class Client{  
    67.          void Start(){  
    68.                 A a = new A();  
    69.                 Debug.Log(a.depend1(new B()));  
    70.                 Debug.Log(a.depend2(new B()));  
    71.                 Debug.Log(a.depend3(new B()));  
    72.                    
    73.                 C c = new C();  
    74.                 Debug.Log(c.depend1(new D()));  
    75.                 Debug.Log(c.depend2(new D()));  
    76.                 Debug.Log(c.depend3(new D()));  
    77.         }  
    78. }  

    接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。
    也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
    本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。
    在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。
    接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。

    说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。
    其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离;其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。

    采用接口隔离原则对接口进行约束时,要注意以下几点:
    1.接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
    2.为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
    3.提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

    运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

    设计模式六大原则(5):迪米特法则

    定义:一个对象应该对其他对象保持最少的了解。
    类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
    因此,尽量降低类与类之间的耦合。

    自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。
    无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。
    低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。

    迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。
    通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
    迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:
    每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。
    耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
    举一个例子:有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。
    先来看一下违反迪米特法则的设计。

    [csharp]  view plain  copy
    1. //总公司员工  
    2. class Employee{  
    3.         private String id;  
    4.         public void setId(String id){  
    5.                 this.id = id;  
    6.         }  
    7.         public String getId(){  
    8.                 return id;  
    9.         }  
    10. }  
    11.    
    12. //分公司员工  
    13. class SubEmployee{  
    14.         private String id;  
    15.         public void setId(String id){  
    16.                 this.id = id;  
    17.         }  
    18.         public String getId(){  
    19.                 return id;  
    20.         }  
    21. }  
    22.    
    23. class SubCompanyManager{  
    24.         public List<SubEmployee> getAllEmployee(){  
    25.                 List<SubEmployee> list = new List<SubEmployee>();  
    26.                 for(int i=0; i<100; i++){  
    27.                         SubEmployee emp = new SubEmployee();  
    28.                         //为分公司人员按顺序分配一个ID  
    29.                         emp.setId("分公司"+i);  
    30.                         list.Add(emp);  
    31.                 }  
    32.                 return list;  
    33.         }  
    34. }  
    35.    
    36. class CompanyManager{  
    37.    
    38.         public List<Employee> getAllEmployee(){  
    39.                 List<Employee> list = new List<Employee>();  
    40.                 for(int i=0; i<30; i++){  
    41.                         Employee emp = new Employee();  
    42.                         //为总公司人员按顺序分配一个ID  
    43.                         emp.setId("总公司"+i);  
    44.                         list.Add(emp);  
    45.                 }  
    46.                 return list;  
    47.         }  
    48.            
    49.         public void printAllEmployee(SubCompanyManager sub){  
    50.                 List<SubEmployee> list1 = sub.getAllEmployee();  
    51.         foreach (SubEmployee e in list1)  
    52.             {  
    53.                          Debug.Log(e.getId());  
    54.             }  
    55.                    
    56.    
    57.                 List<Employee> list2 = this.getAllEmployee();  
    58.         foreach (Employee e in list2)  
    59.             {  
    60.                         Debug.Log(e.getId());  
    61.                 }  
    62.         }  
    63. }  
    64.    
    65. public class Client{  
    66.         void Start(){  
    67.                 CompanyManager e = new CompanyManager();  
    68.                 Debug.Log(e.printAllEmployee(new SubCompanyManager()));  
    69.         }  
    70. }  

    现在这个设计的主要问题出在CompanyManager中,根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。

    按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下:

    [csharp]  view plain  copy
    1. class SubCompanyManager{  
    2.         public List<SubEmployee> getAllEmployee(){  
    3.                 List<SubEmployee> list = new List<SubEmployee>();  
    4.                 for(int i=0; i<100; i++){  
    5.                         SubEmployee emp = new SubEmployee();  
    6.                         //为分公司人员按顺序分配一个ID  
    7.                         emp.setId("分公司"+i);  
    8.                         list.Add(emp);  
    9.                 }  
    10.                 return list;  
    11.         }  
    12.         public void printEmployee(){  
    13.                 List<SubEmployee> list = this.getAllEmployee();  
    14.         foreach (SubEmployee e in list)  
    15.             {  
    16.                  Debug.Log(e.getId());  
    17.             }  
    18.         }  
    19. }  
    20.    
    21. class CompanyManager{  
    22.         public List<Employee> getAllEmployee(){  
    23.                 List<Employee> list = new List<Employee>();  
    24.                 for(int i=0; i<30; i++){  
    25.                         Employee emp = new Employee();  
    26.                         //为总公司人员按顺序分配一个ID  
    27.                         emp.setId("总公司"+i);  
    28.                         list.Add(emp);  
    29.                 }  
    30.                 return list;  
    31.         }  
    32.            
    33.         public void printAllEmployee(SubCompanyManager sub){  
    34.                 sub.printEmployee();  
    35.                 List<Employee> list2 = this.getAllEmployee();  
    36.         foreach (Employee e in list2)  
    37.             {  
    38.                 Debug.Log(e.getId());  
    39.             }  
    40.         }  
    41. }  

    修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

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

    设计模式六大原则(6):开闭原则


    定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
    在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
    因此,当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

    闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。
    以前,如果有人告诉我“你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。

    在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。
    其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。
    也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;
    如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。

    其实,开闭原则无非就是想表达这样一层意思:
    用抽象构建框架,用实现扩展细节。
    因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。
    而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。
    当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

    转载地址:http://www.taidous.com/thread-25633-1-1.html
    展开全文
  • 设计模式六大原则——SOLID

    万次阅读 多人点赞 2018-08-12 20:34:32
    设计模式六大原则有: Single Responsibility Principle:单一职责原则 Open Closed Principle:开闭原则 Liskov Substitution Principle:里氏替换原则 Law of Demeter:迪米特法则 Interface Segregation ...

    SOLID

    设计模式的六大原则有:

    • Single Responsibility Principle:单一职责原则
    • Open Closed Principle:开闭原则
    • Liskov Substitution Principle:里氏替换原则
    • Law of Demeter:迪米特法则
    • Interface Segregation Principle:接口隔离原则
    • Dependence Inversion Principle:依赖倒置原则

    把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计

    下面我们来分别看一下这六大设计原则。

    单一职责原则(Single Responsibility Principle)


    单一职责原则简称 SRP ,顾名思义,就是一个类只负责一个职责。它的定义也很简单:

    There should never be more than one reason for a class to change.

    这句话很好理解,就是说,不能有多个导致类变更的原因。

    那这个原则有什么用呢,它让类的职责更单一。这样的话,每个类只需要负责自己的那部分,类的复杂度就会降低。如果职责划分得很清楚,那么代码维护起来也更加容易。试想如果所有的功能都放在了一个类中,那么这个类就会变得非常臃肿,而且一旦出现bug,要在所有代码中去寻找;更改某一个地方,可能要改变整个代码的结构,想想都非常可怕。当然一般时候,没有人会去这么写的。

    当然,这个原则不仅仅适用于类,对于接口和方法也适用,即一个接口/方法,只负责一件事,这样的话,接口就会变得简单,方法中的代码也会更少,易读,便于维护。

    事实上,由于一些其他的因素影响,类的单一职责在项目中是很难保证的。通常,接口和方法的单一职责更容易实现。

    单一原则的好处:

    • 代码的粒度降低了,类的复杂度降低了。
    • 可读性提高了,每个类的职责都很明确,可读性自然更好。
    • 可维护性提高了,可读性提高了,一旦出现 bug ,自然更容易找到他问题所在。
    • 改动代码所消耗的资源降低了,更改的风险也降低了。

    里氏替换原则(Liskov Substitution Principle)

    里氏替换原则的定义如下:

    Functions that use use pointers or references to base classes must be able to use objects of derived classes without knowing it. 
    

    翻译过来就是,所有引用基类的地方必须能透明地使用其子类的对象。

    里氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的 继承 特性密切相关的。

    这是为什么呢?由于面向对象语言的继承特性,子类拥有父类的所有方法,因此,将基类替换成具体的子类,子类也可以调用父类中的方法(其实是它自己的方法,继承于父类),但是如果要保证完全可以调用,光名称相同不行,还需要满足下面两个条件:

    • 子类中的方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松。
    • 子类中的方法的后置条件必须与超类中被覆写的方法的后置条件相同或更严格。

    这样的话,调用就没有问题了。否则,我在父类中传入一个 List 类型的参数,子类中重写的方法参数却变为 ArrayList ,那客户端使用的时候传入一个 LinkedList 类型的参数,使用父类的时候程序正常运行,但根据 LSP 原则,替换成子类后程序就会出现问题。同理,后置条件也是如此。

    是不是很好理解?

    但是这个原则并不是这么简单的,语法上是肯定不能有任何问题。但是,同时,功能上也要和替换前相同。

    那么这就有些困难了,因为子类重写父类中的方法天经地义,子类中的方法不同于父类中的方法也是很正常的,但是,如果这么随便重写父类中的方法的话,那么肯定会违背 LSP 原则,可以看下面的例子:

    首先有一个父类,水果类:

    public class Fruit {
        void introduce() {
            System.out.println("我是水果父类...");
        }
    }

    其次,它有一个子类,苹果类:

    public class Apple extends Fruit {
        @Override
        void introduce() {
            System.out.println("我是水果子类——苹果");
        }
    }

    客户端代码如下:

    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        HashMap map = new HashMap<>();
        fruit.introduce();
    }

    运行结果:

    我是水果父类...
    

    那么,如果按照 LSP 原则,所有父类出现的地方都能换成其子类,代码如下:

    public static void main(String[] args) {
        Apple fruit = new Apple();
        HashMap map = new HashMap<>();
        fruit.introduce();
    }

    那么运行结果就会变成:

    我是水果子类——苹果
    

    与原来的输出不同,程序的功能被改变了,违背了 LSP 原则。

    因此,可以看到, LSP 原则最重要的一点就是:避免子类重写父类中已经实现的方法。这就是 LSP 原则的本质。这里由于 Fruit 父类已经实现了 introduce 方法,因此子类应该避免再对其进行重写,如果需要增加个性化,就应该对父类进行扩展,而不是重写,否则也会违背开闭原则。

    一般来讲,程序中父类大多是抽象类,因为父类只是一个框架,具体功能还需要子类来实现。因此很少直接去 new 一个父类。而如果出现这种情况,那么就说明父类中实现的代码已经很好了,子类只需要对其进行扩展就会,尽量避免对其已经实现的方法再去重写。

    依赖倒置原则(Dependence Inversion Principle)


    依赖倒置原则的原始定义是这样的:

    High level modules should not depend upon low level modules. 
    Both should depend upon abstractions. 
    Abstractions should not depend upon details. Details should depend upon abstractions.
    

    翻译一下,就是下面三句话:

    • 高层模块不应该依赖底层模块,两者都应该依赖其抽象。
    • 抽象不应该依赖细节。
    • 细节应该依赖抽象。

    在Java语言中的表现就是:

    • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
    • 接口或抽象类不依赖于实现类。
    • 实现类依赖于接口或抽象类。

    简而言之,我们要尽可能使用接口或抽象类。也就是“面向接口编程” 或者说 “面向抽象编程” ,也就是说程序中要尽可能使用抽象类或是接口。

    可能现在还没有使用抽象的习惯,可以看一个例子:比如我们要写一个人吃苹果的程序,首先创建一个苹果类:

    public class Apple {
        public void eaten() {
            System.out.println("正在吃苹果...");
        }
    }
    

    然后写人的类:

    public class Person {
        void eat(Apple apple) {
            apple.eaten();
        }   
    }

    这样,在客户端中我们就可以这样调用:

    Person xiaoMing = new Person();
    Apple apple = new Apple();
    xiaoMing.eat(apple);

    但是这样就有一个问题,如果我不仅仅想吃苹果,还想吃橘子怎么办,在 Person 类中再加一个函数处理橘子?那么吃别的水果呢??总不能程序中每增加一种水果就要在其中增加一个函数吧。

    这时候就需要接口了(抽象类也是可以的)

    程序就可以这样更改,增加一个水果接口:

    public interface Fruit {
        void eaten(); 
    }

    让苹果类实现该接口:

    public class Apple implements Fruit {
        @Override
        public void eaten() {
            System.out.println("正在吃苹果...");
        }
    }

    然后将Person类中的函数的参数稍作修改:

    public class Person {
        void eat(Fruit fruit) {
            fruit.eaten();
        }
    }
    

    这样,客户端代码也稍作修改:

    Person xiaoMing = new Person();
    Fruit apple = new Apple();
    xiaoMing.eat(apple);

    这样改完之后,如果再增加一种新的水果,就不需要改变 Person 类了,方便吧。

    那么再回来说我们的依赖倒置原则,依赖就是类与类之间的依赖,主要是函数传参,上面的例子已经很明白地介绍了,参数要尽可能使用抽象类或接口,这就是“高层模块不应该依赖底层模块,两者都应该依赖其抽象” 的解释。那么如果要实现这个,就要求每个实现类都应该尽可能从抽象中派生,这就是上面的 “细节应该依赖抽象”

    简而言之,该原则主要有下面几点要求:

    • 每个类都尽量要有接口或抽象类,或者两者都有
    • 变量的表面类型尽量是接口或者抽象类(比如程序中的 Fruit apple = new Apple() Fruit 是表面类型, Apple 是实际类型)
    • 任何类都不应该从具体类中派生

    接口隔离原则(Interface Segregation Principle)


    首先声明,该原则中的接口,是一个泛泛而言的接口,不仅仅指Java中的接口,还包括其中的抽象类。

    首先,给出该原则的定义,该原则有两个定义:

    1. Clients should not be forced to depend upon interfaces that they don`t use.

      客户端不应该依赖它不需要的接口。

    2. The dependency of one class to another one should depend on the smallest possible.

      类间的依赖关系应该建立在最小的接口上。

    这是什么意思呢,这是让我们把接口进行细分。举个例子,如果一个类实现一个接口,但这个接口中有它不需要的方法,那么就需要把这个接口拆分,把它需要的方法提取出来,组成一个新的接口让这个类去实现,这就是接口隔离原则。简而言之,就是说,接口中的所有方法对其实现的子类都是有用的。否则,就将接口继续细分。

    看起来,该原则与单一职责原则很相像。确实很像,二者都是强调要将接口进行细分,只不过分的方式不同。单一职责原则是按照 职责 进行划分接口的;而接口隔离原则则是按照实现类对方法的使用来划分的。可以说,接口隔离原则更细一些。

    要想完美地实现该原则,基本上就需要每个实现类都有一个专用的接口。但实际开发中,这样显然是不可能的,而且,这样很容易违背单一职责原则(可能出现同一个职责分成了好几个接口的情况),因此我们能做的就是尽量细分。

    该原则主要强调两点:

    1. 接口尽量小。

      就像前面说的那样,接口中只有实现类中有用的方法。

    2. 接口要高内聚

      就是说,在接口内部实现的方法,不管怎么改,都不会影响到接口外的其他接口或是实现类,只能影响它自己。

    迪米特法则(Law of Demeter)


    迪米特法则也叫最少知道原则(Least Knowledge Principle, LKP ),虽然名称不同,但都是同一个意思:一个对象应该对其他对象有最少的了解。

    该原则也很好理解,我们在写一个类的时候,应该尽可能的少暴露自己的接口。什么意思呢?就是说,在写类的时候,能不 public 就不 public ,所有暴露的属性或是接口,都是不得不暴露的,这样的话,就能保证其他类对这个类有最少的了解了。

    这个原则也没什么需要多讲的,调用者只需要知道被调用者公开的方法就好了,至于它内部是怎么实现的或是有其他别的方法,调用者并不关心,调用者只关心它需要用的。反而,如果被调用者暴露太多不需要暴露的属性或方法,那么就可能导致调用者滥用其中的方法,或是引起一些其他不必要的麻烦。

    开闭原则(Open Closed Principle)


    开闭原则所有设计模式原则中,最基础的那个原则。首先,还是先来看一下它的定义:

    Software entities like classes, modules and functions should be open for extension but closed for modifications.
    

    翻译过来就是:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。

    开闭原则之前也提到过,在 LSP 中,我们说,要避免子类重写父类中已经实现的方法。这个时候,继承父类就是对其进行扩展,但没有进行修改。这就是开闭原则一个很好的体现。

    那是不是开闭原则与 LSP 原则混淆了呢?并不是, LSP 原则强调的是基类与子类的关系,只是其中的一种实现方式用到了开闭原则而已。

    那么开闭原则具体是什么呢?可以说,开闭原则贯穿于以上五个设计模式原则。开闭原则中的对扩展开放,就是说,如果在项目中添加一个功能的时候,可以直接对代码进行扩展;如果要修改某一部分的功能时,我们应该做的是,尽量少做修改(完全不修改是不可能的),但是修改的时候,要保留原来的功能,只是在上面扩展出新的功能,就像版本更新一样,更新后,依然支持旧版本。

    开闭原则是一个特别重要的原则,无论是在设计模式中还是在其他领域中,这都是一个非常基础的设计理念。

    总结

    总而言之,这六个设计模式原则是以后学习设计模式的基础,它们的共同目的就是 SOLID ——建立稳定、灵活、健壮的设计。

    但是原则归原则,有时候由于种种原因,这些条条框框是不得不去打破的,一味地遵循它是不会有好果子吃的(就像接口隔离原则,不可能创建那么多的接口)。因此我们应该正确使用这些原则,主要目的还是为了我们软件的稳定性、灵活性、健壮性和可维护性。

    注:本文所有原则定义皆出自《设计模式之禅》。

    展开全文
  • 分别就是Java设计模式六大原则和常用的23种设计模式了。本篇是对六大原则的整理。(最后一种是哈姆雷特)1.开闭原则(Open Close Principle)定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。 开放-...

    对于Java看到过一个很有意思的说法:Java有六大心法,23种武功招式。

    分别就是Java设计模式六大原则和常用的23种设计模式了。

    本篇是对六大原则的整理。(最后一种是哈姆雷特)

    1.开闭原则(Open Close Principle)
    定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
        开放-封闭原则的意思就是说,你设计的时候,时刻要考虑,尽量让这个类是足够好,写好了就不要去修改了,如果新需求来,我们增加一些类就完事了,原来的代码能不动则不动。这个原则有两个特性,一个是说“对于扩展是开放的”,另一个是说“对于更改是封闭的”。面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。这就是“开放-封闭原则”的精神所在
        比如,刚开始需求只是写加法程序,很快在client类中完成后,此时变化没有发生,需求让再添加一个减法功能,此时会发现增加功能需要修改原来这个类,这就违背了开放-封闭原则,于是你就应该考虑重构程序,增加一个抽象的运算类,通过一些面向对象的手段,如继承、动态等来隔离具体加法、减法与client耦合,需求依然可以满足,还能应对变化。此时需求要添加乘除法功能,就不需要再去更改client及加减法类,而是增加乘法和除法子类即可。
    绝对的修改关闭是不可能的,无论模块是多么的‘封闭‘,都会存在一些无法对之封闭的变化,既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。在我们最初编写代码时,假设变化不会发生,当变化发生时,我们就创建抽象来隔离以后发生同类的变化。
         我们希望的是在开发工作展开不久就知道可能发生的变化,查明可能发生的变化所等待的时候越长,要创建正确的抽象就越困难。开放-封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出现频繁变化的那些部分做出抽象,然而对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意,拒绝不成熟的抽象和抽象本身一样重要。开放-封闭原则,可以保证以前代码的正确性,因为没有修改以前代码,所以可以保证开发人员专注于将设计放在新扩展的代码上。
    简单的用一句经典的话来说:过去的事已成历史,是不可修改的,因为时光不可倒流,但现在或明天计划做什么,是可以自己决定(即扩展)的。

     

    2.里氏代换原则(Liskov Substitution Principle)
    定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
    定义2:子类型必须能够替换掉它们的父类型。
        描述:一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别,也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化
    例子:在生物学分类上,企鹅是一种鸟,但在编程世界里,企鹅却不能继承鸟。在面向对象设计时,子类拥有父类所有非private的行为和属性,鸟会飞,但企鹅不会飞,所以企鹅不能继承鸟类。
        只有当子类可以替换掉父类,软件单位的功能不受影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为,正是有里氏代换原则,使得继承复用成为了可能。正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展,不然还谈什么扩展开放,修改关闭呢
    里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
    1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    2.子类中可以增加自己特有的方法。
    3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
        看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?
    后果就是:你写的代码出问题的几率将会大大增加。

     

    3.依赖倒转原则(Dependence Inversion Principle)
    定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。即针对接口编程,不要针对实现编程
        依赖倒转其实就是谁也不要依靠谁,除了约定的接口,大家都可以灵活自如。依赖倒转可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计了。如果设计的各个部件或类相互依赖,这样就是耦合度高,难以维护和扩展,这也就体现不出面向对象的好处了。
        依赖倒转原则,好比一个团队,有需求组,开发组,测试组,开发组和测试组都是面对同样的需求后,做自己相应的工作,而不应该是测试组按照开发组理解的需求去做测试用例,也就是说开发组和测试组都是直接面向需求组工作,大家的目的是一样的,保证产品按时上线,需求是不依赖于开发和测试的。
        依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
        依赖倒置原则的中心思想是面向接口编程,传递依赖关系有三种方式,以上的说的是是接口传递,另外还有两种传递方式:构造方法传递和setter方法传递,相信用过Spring框架的,对依赖的传递方式一定不会陌生。
    在实际编程中,我们一般需要做到如下3点:
    低层模块尽量都要有抽象类或接口,或者两者都有。
    变量的声明类型尽量是抽象类或接口。
    使用继承时遵循里氏替换原则。
        总之,依赖倒置原则就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。

     

    4.接口隔离原则(Interface Segregation Principle)
       接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
       说到这里,很多人会觉的接口隔离原则跟单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
    采用接口隔离原则对接口进行约束时,要注意以下几点:
    1. 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
    2. 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
    3. 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
       运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

     

    5.迪米特法则(Law Of Demeter)
        迪米特法则其根本思想,是强调了类之间的松耦合,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成影响,也就是说,信息的隐藏促进了软件的复用。
        自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。
        迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
    一句话总结就是:一个对象应该对其他对象保持最少的了解。

     

    6.单一职责原则(Single Responsibility Principle)
    定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责,应该仅有一个引起它变化的原因
        说到单一职责原则,很多人都会不屑一顾。因为它太简单了。稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。
    遵循单一职责原的优点有:
    1.可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
    2.提高类的可读性,提高系统的可维护性;
    3.变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
    需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都需要遵循这一重要原则。

     

    7.组合/聚合复用原则
    就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的
    该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的。
          其实这里最终要的地方就是区分“has-a”和“is-a”的区别。相对于合成和聚合,
    继承的缺点在于:父类的方法全部暴露给子类。父类如果发生变化,子类也得发生变化。聚合的复用的时候就对另外的类依赖的比较的少。。
    合成/聚合复用
    ① 优点:
    新对象存取成分对象的唯一方法是通过成分对象的接口;
     这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的;
     这种复用支持包装;
    这种复用所需的依赖较少;
    每一个新的类可以将焦点集中在一个任务上;
     这种复用可以在运行时动态进行,新对象可以使用合成/聚合关系将新的责任委派到合适的对象。
    ② 缺点:
    通过这种方式复用建造的系统会有较多的对象需要管理。
    继承复用
    ① 优点:
      新的实现较为容易,因为基类的大部分功能可以通过继承关系自动进入派生类;
      修改或扩展继承而来的实现较为容易。
    ② 缺点:
      继承复用破坏包装,因为继承将基类的实现细节暴露给派生类,这种复用也称为白箱复用;如果基类的实现发生改变,那么派生类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,不够灵活。

    展开全文
  • 文档为23种设计模式中的15种设计模式和设计模式六大原则,里面写的某种模式的优缺点,适用场景,具体代码,注意事项,典型应用。具体写的挺好,希望能帮助你。
  • 补充一下面对对象设计八大原则:前五大原则设计模式的前五大原则相同,为: 1、单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,...

    精彩博客:https://www.cnblogs.com/dolphin0520/p/3919839.html
    补充一下面对对象设计八大原则:前五大原则与设计模式的前五大原则相同,为:

    1、单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

    2、开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

    3、里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

    4、依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

    5、接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

    从第六点开始,二者有了区别:
    设计模式的第六大原则:
    6、迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

    面对对象:
    6、优先使用对象组合,而不是类继承
    • 类继承通常为“白箱复用”,对象组合通常为“黑箱复用” 。
    • 继承在某种程度上破坏了封装性,子类父类耦合度高。
    • 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低

    7、封装变化点
    • 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合

    8、针对接口编程,而不是针对实现编程
    • 不将变量类型声明为某个特定的具体类,而是声明为某个接口。
    • 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
    • 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案

    展开全文
  • 详细介绍了设计模式六大原则,配有示例代码和图片,有开闭原则,单一职责原则,里氏替换原则,依赖倒置原则,接口隔离原则,迪米特法则等等。
  • NULL 博文链接:https://lijie-insist.iteye.com/blog/2190970
  • 设计模式六大原则

    2015-11-24 20:06:45
    设计模式的学习,可以增强自己的代码复用意识。 同时,也可以清晰地表达...本文将介绍设计模式六大原则: • 单一职责原则; • 里氏替换原则; • 依赖倒置原则; • 接口隔离原则; • 迪米特法则; • 开闭原则;
  • 里氏替换原则 开闭原则 依赖倒置原则 接口分离原则 迪米特法则 记忆方法: 一个单身(单一职责)的里氏(里氏替换原则)拿着一把颠倒(依赖倒置)的半开半闭(开闭原则)的扇子,在看接口分离(接口分离)问题,...
  • 设计模式六大原则详解 设计模式原则详解 设计模式原则详解
  • 【iOS】快速理解 设计模式六大原则

    千次阅读 2018-02-05 16:40:47
    1、单一职责原则(Single Responsibility Principle,简称SRP ) 核心思想: 应该有且仅有一个原因引起类的变更 问题描述: 假如有类Class1完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该...
  • 之前我们对设计模式六大原则做了简单归纳,这篇博客是对开放封闭原则进行的举例说明。 开放封闭原则的意义软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已...
  • 代码重构 +设计模式六大原则 + 23种设计模式

    万次阅读 多人点赞 2015-01-20 15:17:23
    设计模式六大原则(3):依赖倒置原则 定义: 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 问题由来: 类A直接依赖类B,假如要将类A改为依赖类C,则...
  • 个人整理的比较全面的 设计模式六大原则与类的六种关系

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 79,424
精华内容 31,769
关键字:

设计模式六大原则