详解设计模式六大原则

设计模式(Design pattern)是一套被反复使用、多数人知晓的、通过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构同样。javascript

借用并改编一下鲁迅老师《故乡》中的一句话,一句话归纳设计模式: 但愿本无所谓有,无所谓无.这正如coding的设计模式,其实coding本没有设计模式,用的人多了,也便成了设计模式java

设计模式
v六大原则

设计模式(面向对象)有六大原则:ios

开闭原则具备理想主义的色彩,它是面向对象设计的终极目标。其余几条,则能够看作是开闭原则的实现方法。 设计模式就是实现了这些原则,从而达到了代码复用、增长可维护性的目的。数据库

v开闭原则

1.概念: 编程

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应尽可能在不修改原(是“原”,指原来的代码)代码的状况下进行扩展。

2.模拟场景: 设计模式

在软件的生命周期内,由于变化、升级和维护等缘由须要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使咱们不得不对整个功能进行重构,而且须要原有代码通过从新测试。

3.Solution: app

当软件须要变化时,尽可能经过扩展软件实体的行为来实现变化,而不是经过修改已有的代码来实现变化。

4.注意事项: ide

  • 经过接口或者抽象类约束扩展,对扩展进行边界限定,不容许出如今接口或抽象类中不存在的public方法
  • 参数类型、引用对象尽可能使用接口或者抽象类,而不是实现类
  • 抽象层尽可能保持稳定,一旦肯定即不容许修改

5.开闭原则的优势: 函数

  • 可复用性
  • 可维护性

6.开闭原则图解: 工具

开闭原则
v里氏代换原则

1.概述: 派生类(子类)对象可以替换其基类(父类)对象被调用

2.概念: 

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类能够出现的地方,子类必定能够出现。 LSP是继承复用的基石,只有当衍生类能够替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也可以在基类的基础上增长新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,因此里氏代换原则是对实现抽象化的具体步骤的规范。(源自百度百科)

3.子类为何能够替换父类的位置?: 

当知足继承的时候,父类确定存在非私有成员,子类确定是获得了父类的这些非私有成员(假设,父类的的成员所有是私有的,那么子类没办法从父类继承任何成员,也就不存在继承的概念了)。既然子类继承了父类的这些非私有成员,那么父类对象也就能够在子类对象中调用这些非私有成员。因此,子类对象能够替换父类对象的位置。

4.里氏代换原则优势: 

需求变化时,只须继承,而别的东西不会改变。因为里氏代换原则才使得开放封闭成为可能。这样使得子类在父类无需修改的话就能够扩展。

5.里氏代换原则Demo: 

代码正文:

//------------------------------------------------------------------------------
// <copyright file="Program.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------

namespace TestApp
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Transportation transportation = new Transportation();
            transportation.Say();
            Transportation sedan = new Sedan();
            sedan.Say();
            Console.ReadKey();
        }
    }

    class Transportation
    {
        public Transportation()
        {
            Console.WriteLine("Transportation?");
        }

        public virtual void Say()
        {
            Console.WriteLine("121");
        }
    }

    class Sedan:Transportation
    {
        public Sedan()
        {
            Console.WriteLine("Transportation:Sedan");
        }

        public override void Say()
        {
            Console.WriteLine("Sedan");
        }
    }

    class Bicycles : Transportation
    {
        public Bicycles()
        {
            Console.WriteLine("Transportation:Bicycles");
        }

        public override void Say()
        {
            Console.WriteLine("Bicycles");
        }
    }
}

代码效果:

6.里氏代换原则图解: 

里氏代换原则
v依赖倒转原则

1.概念: 

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就下降了客户与实现模块间的耦合。

2.依赖倒转原则用处: 

有些时候为了代码复用,通常会把经常使用的代码写成函数或类库。这样开发新项目时,直接用就好了。好比作项目时大多要访问数据库,因此咱们就把访问数据库的代码写成了函数。每次作项目去调用这些函数。那么咱们的问题来了。咱们要作新项目时,发现业务逻辑的高层模块都是同样的,但客户却但愿使用不一样的数据库或存储住处方式,这时就出现麻烦了。咱们但愿能再次利用这些高层模块,但高层模块都是与低层的访问数据库绑定在一块儿,没办法复用这些高层模块。因此无论是高层模块和低层模块都应该依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个更改都不用担忧了。

3.注意事项: 

  • 高层模块不该该依赖低层模块。两个都应该依赖抽象。
  • 抽象不该该依赖细节。细节应该依赖抽象。

4.模拟场景: 

场景:

假设如今须要一个Monitor工具,去运行一些已有的APP,自动化来完成咱们的工做。Monitor工具须要启动这些已有的APP,而且写下Log。

代码实现1:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;

    public class AppOne
    {
        public bool Start()
        {
            Console.WriteLine("1号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("1号APP输出日志");
            return true;
        }
    }

    public class AppTwo
    {
        public bool Start()
        {
            Console.WriteLine("2号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("2号APP输出日志");
            return true;
        }
    }

    public class Monitor
    {
        public enum AppNumber
        {
            AppOne=1,
            AppTwo=2
        }

        private AppOne appOne = new AppOne();
        private AppTwo appTwo = new AppTwo();
        private AppNumber number;
        public Monitor(AppNumber number)
        {
            this.number = number;
        }

        public bool StartApp()
        {
            return number == AppNumber.AppOne ? appOne.Start() : appTwo.Start();
        }

        public bool ExportAppLog()
        {
            return number == AppNumber.AppOne ? appOne.ExportLog() : appTwo.ExportLog();
        }
    }
}

代码解析1:

在代码实现1中咱们已经轻松实现了Monitor去运行已有APP而且写下LOG的需求。而且代码已经上线了.

春...夏...秋...冬...

春...夏...秋...冬...

春...夏...秋...冬...

就这样,三年过去了。

一天客户找上门了,公司业务扩展了,如今须要新加3个APP用Monitor自动化。这样咱们就必须得改Monitor。

代码实现2:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    public class Monitor
    {
        public enum AppNumber
        {
            AppOne = 1,
            AppTwo = 2,
            AppThree = 3,
            AppFour = 4,
            AppFive = 5
        }

        private AppOne appOne = new AppOne();
        private AppTwo appTwo = new AppTwo();
        private AppThree appThree = new AppThree();
        private AppFour appFour = new AppFour();
        private AppFive appFive = new AppFive();
        private AppNumber number;
        public Monitor(AppNumber number)
        {
            this.number = number;
        }

        public bool StartApp()
        {
            bool result = false;
            if (number == AppNumber.AppOne)
            {
                result = appOne.Start();
            }
            else if (number == AppNumber.AppTwo)
            {
                result = appTwo.Start();
            }
            else if (number == AppNumber.AppThree)
            {
                result = appThree.Start();
            }
            else if (number == AppNumber.AppFour)
            {
                result = appFour.Start();
            }
            else if (number == AppNumber.AppFive)
            {
                result = appFive.Start();
            }

            return result;
        }

        public bool ExportAppLog()
        {
            bool result = false;
            if (number == AppNumber.AppOne)
            {
                result = appOne.ExportLog();
            }
            else if (number == AppNumber.AppTwo)
            {
                result = appTwo.ExportLog();
            }
            else if (number == AppNumber.AppThree)
            {
                result = appThree.ExportLog();
            }
            else if (number == AppNumber.AppFour)
            {
                result = appFour.ExportLog();
            }
            else if (number == AppNumber.AppFive)
            {
                result = appFive.ExportLog();
            }

            return result;
        }
    }
}

代码解析2:

这样会给系统添加新的相互依赖。而且随着时间和需求的推移,会有更多的APP须要用Monitor来监测,这个Monitor工具也会被愈来愈对的if...else撑爆炸,并且代码随着APP越多,越难维护。最终会致使Monitor走向灭亡(下线)。

介于这种状况,能够用Monitor这个模块来生成其它的程序,使得系统可以用在须要的APP上。OOD给咱们提供了一种机制来实现这种“依赖倒置”。

代码实现3:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;

    public interface IApp
    {
        bool Start();
        bool ExportLog();
    }

    public class AppOne : IApp
    {
        public bool Start()
        {
            Console.WriteLine("1号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("1号APP输出日志");
            return true;
        }
    }

    public class AppTwo : IApp
    {
        public bool Start()
        {
            Console.WriteLine("2号APP开始启动");
            return true;
        }

        public bool ExportLog()
        {
            Console.WriteLine("2号APP输出日志");
            return true;
        }
    }

    public class Monitor
    {
        private IApp iapp;
        public Monitor(IApp iapp)
        {
            this.iapp = iapp;
        }

        public bool StartApp()
        {
            return iapp.Start();
        }

        public bool ExportAppLog()
        {
            return iapp.ExportLog();
        }
    }
}

代码解析3:

如今Monitor依赖于IApp这个接口,而与具体实现的APP类没有关系,因此不管再怎么添加APP都不会影响到Monitor自己,只须要去添加一个实现IApp接口的APP类就能够了。

v接口隔离原则

1.概念: 

客户端不该该依赖它不须要的接口,类间的依赖关系应该创建在最小的接口上

2.含义: 

接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),可是“小”是有限度的,首先就是不能违反单一职责原则。

3.模拟场景: 

一个OA系统,外部只负责提交和撤回工做流,内部负责审核和驳回工做流。

4.代码演示: 

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    public interface IReview
    {
        void ReviewWorkFlow();

        void RejectWorkFlow();
    }

    public class Review : IReview
    {
        public void ReviewWorkFlow()
        {
            Console.WriteLine("开始审核工做流");
        }

        public void RejectWorkFlow()
        {
            Console.WriteLine("已经驳回工做流");
        }
    }

    public interface ISubmit
    {
        void SubmitWorkFlow();

        void CancelWorkFlow();
    }

    public class Submit : ISubmit
    {
        public void SubmitWorkFlow()
        {
            Console.WriteLine("开始提交工做流");
        }

        public void CancelWorkFlow()
        {
            Console.WriteLine("已经撤销工做流");
        }
    }
}

5.代码解析: 

其实接口隔离原则很好理解,在上面的例子里能够看出来,若是把OA的外部和内部都定义一个接口的话,那这个接口会很大,并且实现接口的类也会变得臃肿。

接口隔离原则
v合成/聚合复用原则

1.概念: 

合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)常常又叫作合成复用原则。合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象经过向这些对象的委派达到复用已有功能的目的。它的设计原则是:要尽可能使用合成/聚合,尽可能不要使用继承。

2.合成/聚合解析: 

  • 聚合概念: 

    聚合用来表示“拥有”关系或者总体与部分的关系。表明部分的对象有可能会被多个表明总体的对象所共享,并且不必定会随着某个表明总体的对象被销毁或破坏而被销毁或破坏,部分的生命周期能够超越总体。例如,Iphone5和IOS,当Iphone5删除后,IOS还能存在,IOS能够被Iphone6引用。

    聚合关系UML类图: 

    合成/聚合复用原则

    代码演示: 

    //------------------------------------------------------------------------------
    // <copyright file="Dependency.cs" company="CNBlogs Corporation">
    //     Copyright (C) 2015-2016 All Rights Reserved
    //     原博文地址: http://www.cnblogs.com/toutou/
    //     做      者: 请叫我头头哥
    // </copyright> 
    //------------------------------------------------------------------------------
    namespace TestLibrary.ExtensionsClass
    {
        class IOS
        { 
        }
    
        class Iphone5
        {
            private IOS ios;
            public Iphone5(IOS ios)
            {
                this.ios = ios;
            }
        }
    }
  • 合成概念: 

    合成用来表示一种强得多的“拥有”关系。在一个合成关系里,部分和总体的生命周期是同样的。一个合成的新对象彻底拥有对其组成部分的支配权,包括它们的建立和湮灭等。使用程序语言的术语来讲,合成而成的新对象对组成部分的内存分配、内存释放有绝对的责任。一个合成关系中的成分对象是不能与另外一个合成关系共享的。一个成分对象在同一个时间内只能属于一个合成关系。若是一个合成关系湮灭了,那么全部的成分对象要么本身湮灭全部的成分对象(这种状况较为广泛)要么就得将这一责任交给别人(较为罕见)。例如:水和鱼的关系,当水没了,鱼也不可能独立存在。

    合成关系UML类图: 

    合成/聚合复用原则

    代码演示: 

    //------------------------------------------------------------------------------
    // <copyright file="Dependency.cs" company="CNBlogs Corporation">
    //     Copyright (C) 2015-2016 All Rights Reserved
    //     原博文地址: http://www.cnblogs.com/toutou/
    //     做      者: 请叫我头头哥
    // </copyright> 
    //------------------------------------------------------------------------------
    namespace TestLibrary.ExtensionsClass
    {
        using System;
    
        class Fish
        {
            public Fish CreateFish()
            {
                Console.WriteLine("一条小鱼儿");
                return new Fish();
            }
        }
    
        class Water
        {
            private Fish fish;
            public Water()
            {
                fish = new Fish();
            }
    
            public void CreateWater()
            {
                // 当建立了一个水的地方,那这个地方也得放点鱼进去
                fish.CreateFish();
            }
        }
    }

3.模拟场景: 

好比说咱们先摇到号(这个比较困难)了,须要为本身买一辆车,若是4S店里的车默认的配置都是同样的。那么咱们只要买车就会有这些配置,这时使用了继承关系:

合成/聚合复用原则

不可能全部汽车的配置都是同样的,因此就有SUV和小轿车两种(只列举两种比较热门的车型),而且使用机动车对它们进行聚合使用。这时采用了合成/聚合的原则:

合成/聚合复用原则
v迪米特法则

1.概念: 

一个软件实体应当尽量少的与其余实体发生相互做用。每个软件单位对其余的单位都只有最少的知识,并且局限于那些与本单位密切相关的软件单位。迪米特法则的初衷在于下降类之间的耦合。因为每一个类尽可能减小对其余类的依赖,所以,很容易使得系统的功能模块功能独立,相互之间不存在(或不多有)依赖关系。迪米特法则不但愿类之间创建直接的联系。若是真的有须要创建联系,也但愿能经过它的友元类来转达。所以,应用迪米特法则有可能形成的一个后果就是:系统中存在大量的中介类,这些类之因此存在彻底是为了传递类之间的相互调用关系——这在必定程度上增长了系统的复杂度。

2.模拟场景: 

场景:公司财务总监发出指令,让财务部门的人去统计公司已发公司的人数。

一个常态的编程:(确定是不符LoD的反例)

UML类图:

迪米特法则

代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// 财务总监
    /// </summary>
    public class CFO
    {
        /// <summary>
        /// 财务总监发出指令,让财务部门统计已发工资人数
        /// </summary>
        public void Directive(Finance finance)
        {
            List<Employee> employeeList = new List<Employee>();
            // 初始化已发工资人数
            for (int i = 0; i < 500; i++)
            {
                employeeList.Add(new Employee());
            }

            // 转告财务部门开始统计已结算公司的员工
            finance.SettlementSalary(employeeList);
        }
    }

    /// <summary>
    /// 财务部
    /// </summary>
    public class Finance
    {
        /// <summary>
        /// 统计已结算公司的员工
        /// </summary>
        public void SettlementSalary(List<Employee> employeeList) 
        {
            Console.WriteLine(string.Format("已结算工资人数:{0}", employeeList.Count));
        }
    }

    /// <summary>
    /// 员工
    /// </summary>
    public class Employee
    {

    }

    /// <summary>
    /// 主程序
    /// </summary>
    public class Runner
    {
        public static void main(String[] args)
        {
            CFO cfo = new CFO();
            // 财务总监发出指令
            cfo.Directive(new Finance());
        }
    }
}

根据模拟的场景:财务总监让财务部门总结已发工资的人数。 财务总监和员工是陌生关系(即总监不须要对员工执行任何操做)。根据上述UML图和代码解决办法显然能够看出,上述作法违背了LoD法则。

依据LoD法则解耦:(符合LoD的例子)

UML类图:

代码演示:

//------------------------------------------------------------------------------
// <copyright file="Dependency.cs" company="CNBlogs Corporation">
//     Copyright (C) 2015-2016 All Rights Reserved
//     原博文地址: http://www.cnblogs.com/toutou/
//     做      者: 请叫我头头哥
// </copyright> 
//------------------------------------------------------------------------------
namespace TestLibrary.ExtensionsClass
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// 财务总监
    /// </summary>
    public class CFO
    {
        /// <summary>
        /// 财务总监发出指令,让财务部门统计已发工资人数
        /// </summary>
        public void Directive(Finance finance)
        {
            // 通知财务部门开始统计已结算公司的员工
            finance.SettlementSalary();
        }
    }

    /// <summary>
    /// 财务部
    /// </summary>
    public class Finance
    {
        private List<Employee> employeeList;  

        //传递公司已工资的人
        public Finance(List<Employee> _employeeList)
        {
            this.employeeList = _employeeList;  
    }  

        /// <summary>
        /// 统计已结算公司的员工
        /// </summary>
        public void SettlementSalary() 
        {
            Console.WriteLine(string.Format("已结算工资人数:{0}", employeeList.Count));
        }
    }

    /// <summary>
    /// 员工
    /// </summary>
    public class Employee
    {

    }

    /// <summary>
    /// 主程序
    /// </summary>
    public class Runner
    {
        public static void main(String[] args)
        {
            List<Employee> employeeList = new List<Employee>();

            // 初始化已发工资人数
            for (int i = 0; i < 500; i++)
            {
                employeeList.Add(new Employee());
            }

            CFO cfo = new CFO();

            // 财务总监发出指令
            cfo.Directive(new Finance(employeeList));
        }
    }
}

根据LoD原则咱们须要让财务总监和员工之间没有之间的联系。这样才是遵照了迪米特法则。

v博客总结

想搞懂设计模式,必须先知道设计模式遵循的六大原则,不管是哪一种设计模式都会遵循一种或者多种原则。这是面向对象不变的法则。本文针对的是设计模式(面向对象)主要的六大原则展开的讲解,并尽可能作到结合实例和UML类图,帮助你们理解。在后续的博文中还会跟进一些设计模式的实例。

 


做  者:请叫我头头哥
出  处:http://www.cnblogs.com/toutou/
关于做者:专一于基础平台的项目开发。若有问题或建议,请多多赐教!
版权声明:本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文连接。
特此声明:全部评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信
声援博主:若是您以为文章对您有帮助,能够点击文章右下角推荐一下。您的鼓励是做者坚持原创和持续写做的最大动力!

相关文章
相关标签/搜索