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

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

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

比如:类T只负责一个职责P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。

举个例子:有个动物呼吸类

 public class Animal
{
private string _Name = null;
public Animal(string name)
{
this._Name = name;
}
public void Breath()
{
Console.WriteLine("{0} 呼吸空气", this._Name);
} }

  

public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.Breath("鸡");
animal.Breath("鱼"); }
}

运行结果:

鸡呼吸空气

鱼呼吸空气

程序上线后,发现问题了,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。修改时如果遵循单一职责原则,代码如下:

public class Fish
{
public void BreathFish()
{
Console.WriteLine("{0} 呼吸水", this._Name);
}
}
public class Chicken
{
        public void BreathChicken()
{
Console.WriteLine("{0} 呼吸空气", this._Name);
}
}

这样做增加了类的成本,

public class Animal
{
 private string _Name = null;
public Animal(string name)
{
this._Name = name;
}
  public void BreathFish()
{
Console.WriteLine("{0} 呼吸水", this._Name);
} public void BreathChicken()
{
Console.WriteLine("{0} 呼吸空气", this._Name);
}
} 

类的角度来说,违背单一职责
如果类的方法不多,逻辑简单,类级别可以违背单一职责

可以看到,这种修改方式要简单的多。但是却存在着隐患,以后如果增加了一些其他动物,则又要去增加一些方法,和一些类,其实我们很大一部分会这样操作

       public void Breath()
{
if (this._Name.Equals("鱼"))
{
Console.WriteLine("{0} 呼吸水", this._Name);
}
else if (this._Name.Equals("鸡"))
{
Console.WriteLine("{0} 呼吸空气", this._Name);
}
else
{
throw new Exception();
}
}

虽然这样违背了单一原则,但是如果逻辑够简单,方法级别可以违背单一职责。

例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。

遵循单一职责原的优点有:

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 提高类的可读性,提高系统的可维护性;
  • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

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

肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑。其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。

定义:所有引用基类的地方必须能透明地使用其子类的对象。

其实关于里氏替换原则,在前面的章节中就有提及过,在这里就重温一遍。

  #region abstract
public abstract class ParentClass
{
/// <summary>
/// CommonMethod
/// </summary>
public void CommonMethod()
{
Console.WriteLine("ParentClass CommonMethod");
} /// <summary>
/// virtual 虚方法 必须包含实现 但是可以被重载
/// </summary>
public virtual void VirtualMethod()
{
Console.WriteLine("ParentClass VirtualMethod");
} } public class ChildClass : ParentClass
{
/// <summary>
/// new 隐藏
/// </summary>
public new void CommonMethod()
{
Console.WriteLine("ChildClass CommonMethod");
} public override void VirtualMethod()
{
Console.WriteLine("ChildClass VirtualMethod");
} } #endregion abstract

  

     public static void Test()
{
Console.WriteLine("*******************************************"); ParentClass instance = new ChildClass();
Console.WriteLine("下面是instance.CommonMethod()");
instance.CommonMethod();
Console.WriteLine("下面是instance.VirtualMethod()");
instance.VirtualMethod(); Console.WriteLine("*******************************************"); }

通过调用结果发现,普通方法调用的是父类,虚方法调用子类。

有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的

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

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

问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

例子:假设张三一开始会使用Nokia手机,手机功能又打电话发短信。

public class Nokia
{
public void Call()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
public void Text()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
}

  

 public class Student
{
public int Id { get; set; }
public string Name { get; set; } public void PlayNokia(Nokia phone)
{
Console.WriteLine("这里是{0}", this.Name);
phone.Call();
phone.Text();
}
}

但是后面手机品种增加,出现了iPhone,honor等手机,张三都会使用。那我们是不是又要新增几个方法。

 public void PlayiPhone(iPhone phone)
{
Console.WriteLine("这里是{0}", this.Name);
phone.Call();
phone.Text();
}
public void PlayHonor(Honor phone)
{
Console.WriteLine("这里是{0}", this.Name);
phone.Call();
phone.Text();
}

  

这样是不是他麻烦了,没增加一款手机都会增加一个方法,对于程序来说这样做太繁冗了。

这个时候增加一个抽象类。使这些手机款式依赖于抽象类

public abstract class AbstractPhone
{
public abstract void Call();
public abstract void Text();
}

这样不管增加多少款手机,方法都不会增加

  public void PlayPhone(AbstractPhone phone)
{
Console.WriteLine("这里是{0}", this.Name);
phone.Call();
phone.Text();
}

上端程序只要调用这一个方法就可以代替其他方法,只有手机类依赖了这个抽象类。

在实际编程中,我们一般需要做到如下3点:

  • 低层模块尽量都要有抽象类或接口,或者两者都有。
  • 变量的声明类型尽量是抽象类或接口。
  • 使用继承时遵循里氏替换原则。

依赖倒置原则的核心就是要我们面向接口、抽象编程,理解了面向接口、抽象编程,也就理解了依赖倒置

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

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

例子,还是手机这个类,定义了一个接口,一款手机又许多功能

 public interface IExtend //: IExtendAdvanced
{
void Photo();
void Online();
void Game();
void Record();
void Movie();
void Map();
void Pay();
}
  public class iPhone : IExtend
public override void Call()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
public override void Text()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
} public void Photo()
{
Console.WriteLine("User {0} Photo", this.GetType().Name);
} public void Online()
{
Console.WriteLine("User {0} Online", this.GetType().Name);
} public void Game()
{
Console.WriteLine("User {0} Game", this.GetType().Name);
} public void Map()
{
Console.WriteLine("User {0} Map", this.GetType().Name);
} public void Pay()
{
Console.WriteLine("User {0} Pay", this.GetType().Name);
} public void Record()
{
Console.WriteLine("User {0} Record", this.GetType().Name);
} public void Movie()
{
Console.WriteLine("User {0} Movie", this.GetType().Name);
}
}

  

但是像以前的Nokia就不支持支付功能,但是他也继承了这个功能,这就会出现问题了,

可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口进行拆分。在这里我们将原有的接口拆分

 public interface IExtend
{
void Photo();
void Online();
void Game();
void Record();
void Movie();
} public interface IExtendAdvanced
{
void Map();
void Pay();
}

接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

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

采用接口隔离原则对接口进行约束时,要注意以下几点:

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

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

未完待续。。。

  

  

DesignPatternPrinciple(设计模式原则)一的更多相关文章

  1. C#软件设计——小话设计模式原则之:依赖倒置原则DIP

    前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...

  2. C#软件设计——小话设计模式原则之:单一职责原则SRP

    前言:上篇C#软件设计——小话设计模式原则之:依赖倒置原则DIP简单介绍了下依赖倒置的由来以及使用,中间插了两篇WebApi的文章,这篇还是回归正题,继续来写写设计模式另一个重要的原则:单一职责原则. ...

  3. C#软件设计——小话设计模式原则之:接口隔离原则ISP

    前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...

  4. C#软件设计——小话设计模式原则之:开闭原则OCP

    前言:这篇继续来看看开闭原则.废话少说,直接入正题. 软件设计原则系列文章索引 C#软件设计——小话设计模式原则之:依赖倒置原则DIP C#软件设计——小话设计模式原则之:单一职责原则SRP C#软件 ...

  5. 设计模式原则——依赖倒转&里氏代换原则

    设计模式一共有六大原则: 单一原则.开放封闭原则.接口分离原则.里氏替换原则.最少知识原则.依赖倒置原则. 这篇博客是自己对依赖倒转&里氏代换原则的一些拙见,有何不对欢迎大家指出. 依赖倒转原 ...

  6. Java设计模式(二)设计模式原则

    学习Java设计模式之前,有必要先了解设计模式原则. 开闭原则 定义 一个软件实体如类.模块和函数应该对扩展开放,对修改关闭 用抽象构建框架,用实现扩展细节 优点:提高软件系统的可复用性及可维护性 C ...

  7. DesignPatternPrinciple(设计模式原则)二

    设计模式六大原则(5):迪米特法则 定义:一个对象应该对其他对象保持最少的了解. 问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大. 解决方案:尽量降低类与类之 ...

  8. C++技术问题总结-第12篇 设计模式原则

    设计模式六大原则,參见http://www.uml.org.cn/sjms/201211023.asp. 1. 单一职责原则 定义:不要存在多于一个导致类变更的原因.通俗的说,即一个类仅仅负责一项职责 ...

  9. 设计模式原则(6)--Open-Closed Principle(OCP)--开闭原则

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.定义: 一个软件实体应当对扩展开放,对修改关闭.即软件实体应尽量在不修改原有代码的情况下进行扩展. 2.使用场 ...

随机推荐

  1. Java 日志框架终极教程

    概述 对于现代的 Java 应用程序来说,只要被部署到真实的生产环境,其日志的重要性就是不言而喻的,很难想象没有任何日志记录功能的应用程序被运行于生产环境中.日志 API 所能提供的功能是多种多样的, ...

  2. php与MySQL(基本操作)

    PHP连接 MySQL 在我们访问 MySQL 数据库前,我们需要先连接到数据库服务器,连接服务器,我们使用mysqli_connect()函数. 在使用这个函数之前,我们首先来看一下这个函数的语法: ...

  3. nodejs-ORM 操作数据库中间件waterline的使用

    waterline和Sails.js同一团队开发,支持几乎所有的主流数据库,是nodejs下一款非常强大的orm,可以显著提升开发效率 一.waterline支持的数据库 二.waterline的配置 ...

  4. java宠物练习

    先定一个宠物的抽象类,把所有共有的属性方法放到次类中,用于子类去继承实现. package backing2; abstract public class pet { private String n ...

  5. 解决无线网络连接出现黄色感叹号---win10

    今天使用公司的电脑,这个电脑是另一位同事用过的,然后到我这里就连不上网了.然后把自己解决的方法记录一下: 开始运行输入以下命令来重置IP. 打开运行输入:cmd 在命令窗口中输入:ipconfig / ...

  6. linux和Windows实现文件共享之samba的安装与配置

    背景: 项目需求linux的一个目录,需要在两台windows目录上面进行同时共享. 使用mount时发现,通过mount将同一个linux上面的目录挂载在两台windows机器上时,会出现文件隐藏的 ...

  7. (转)Dom4J解析

    xml文档: <?xml version="1.0" encoding="UTF-8"?> <书架> <书 出版社="清 ...

  8. 第一回:Scrapy的试水

    前言:今天算是见到Scrapy的第二天,之前只是偶尔查了查,对于这个框架的各种解释,我-----都-----看------不------懂----,没办法,见面就是刚. 目的:如题,试水 目标:< ...

  9. ui-router ^1.x在ng1中使用state events

    官网信息:https://ui-router.github.io/ng1/docs/latest/modules/ng1_state_events.html Legacy state events P ...

  10. javascript实现朴素贝叶斯分类与决策树ID3分类

    今年毕业时的毕设是有关大数据及机器学习的题目.因为那个时间已经步入前端的行业自然选择使用JavaScript来实现其中具体的算法.虽然JavaScript不是做大数据处理的最佳语言,相比还没有优势,但 ...