[Arch] 02. Design principle and Software Pattern
六大设计原则(C#)【有代码实例讲解,有空看一下】
软件设计的若干原则
前五个重要原则
一、SOLID设计
Figure, 重要的前五个原则
二、原则详解
(1) 单一职责原则 (Simple responsibility pinciple SRP)
类的设计趋向于:Use Case Diagram --> (derived) --> Detail
就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.
(2) 开-闭原则 (Open-Closed Principle, OCP)
/* Software entities should be open for extension, but closed for modification */
反面例子:Dependency: 作为参数,作为局部变量,调用静态方法。
改进方式:增加抽象类或者接口,以及set方法。
再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.
换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。
这是面向对象设计(OOD)的基石,也是最重要的原则。
(3) 里氏代换原则 (Liskov Substitution Principle, LSP)
[replace A with B 用B来代替A]
[substitute A for B 用A来代替B (或者A substitute for B)]
Derived Class 需严格成为 Base Class 的子类型。
首先针对基类编程,在具体实现 或程序运行时再确定具体子类。 <-- 实现开闭原则的重要基础
应当尽量从抽象类继承,而不从具体类继承,
一般而言,如果有两个具体类A, B有继承关系,那么一个最简单的修改方案是建立一个抽象类C,然后让类A和B成为抽象类C的子类.
即如果有一个由继承关系形成的登记结构的话,那么在等级结构的树形图上面所有的树叶节点都应当是具体类;而所有的树枝节点都应当是抽象类或者接口.
(4) 接口隔离原则(Interface Segregation Principle, ISP)
接口杜绝臃肿,以免空方法实现。
倾向瘦接口。
(5) 依赖倒置原则 (Dependence Inversion Principle)
除去set方法,也可采用读取配置文件来选择类的调用。<-- 实现开闭原则的重要手段
接口与抽象的区别就在于抽象类可以提供某些方法的部分实现,而接口则不可以,这也大概是抽象类唯一的优点。
如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就都得到得到了这个新的具体方法,而接口做不到这一点。
第六个原则
一、另一个原则
(*) 合成/聚合复用原则 (Composite/Aggregate Reuse Principle,CARP)
简短的表述:要尽量使用合成/聚合,尽量不要使用继承。
ref: 谈一谈自己对依赖、关联、聚合和组合之间区别的理解【有代码示范】
ref: 类与类之间的几种关系
[总结]
对于继承、实现这两种关系没多少疑问,它们体现的是一种类和类、或者类与接口间的纵向关系。其他的四种关系体现的是类和类、或者类与接口间的引用、横向关系,是比较难区分的,有很多事物间的关系要想准确定位是很难的。前面也提到,这四种关系都是语义级别的,所以从代码层面并不能完全区分各种关系,但总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖。
若 需使用一个类的方法,可以通过 继承 或者 聚合/组合。
优先后者,降低耦合。
二、实例:DAO的实现
既然 getConnection() 可能会变化,那么其所在的类 不便成为基类(Base Class),
然后 采用 构造函数注入、setter方法注入 和 接口注入。从而注入不同的数据库。
三种注入方法
Reference: Sprint IOC 三种注入方法
-
控制反转 (Inverse Of Control)
/* 难理解,日后再说 */
控制反转是一种将 组件依赖关系 的 创建和管理 置于程序外部的技术。
由容器控制程序之间的关系,而不是由代码直接控制。
由于控制权由代码转向了容器,所以称为反转。
Dependency Injection:两个对象之间的依赖关系在程序运行时由外部容器动态的注入依赖行为方式称为依赖注入 (DI) 。 DI 是 IOC 的一种形式。
依赖注入的三种实现类型:接口注入、 Setter注入和构造器注入。
接口注入
public class ClassA { private InterfaceB clzB; public void doSomething()
{ Ojbect obj = Class.forName(Config.BImplementation).newInstance(); clzB = (InterfaceB)obj; clzB.doIt() } …… }
set注入
public class ClassA { private InterfaceB clzB; public void setClzB(InterfaceB clzB) { this. clzB = clzB; } …… }
构造器注入
public class DIByConstructor { private final DataSource dataSource; public DIByConstructor(DataSource ds)
{ this.dataSource = ds; } …… }
面向切面 (Aspect Oriented Programming)
???
/* implement */
第七个原则
(*) 迪米特法则 (Law of Demeter LoD) ,又叫做最少知识原则 (Least Knowledge Principle,LKP)
/* 一个软件实体应当尽可能少地与其他实体发生相互作用 */
添加中间类来降低界面组件之间的耦合度.
一个对象应当对其他对象有尽可能少的了了解,为了降低耦合度。
类间关系
Ref: 类与类之间的几种关系
一、继承关系
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。在Java中继承关系通过关键字extends明确标识,在设计时一般没有争议性。在UML类图设计中,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。
二、实现关系
实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。在Java中此类关系通过关键字implements明确标识,在设计时一般没有争议性。在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。
三、依赖关系
【作为了参数而使用】
简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A。比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。在UML类图设计中,依赖关系用由类A指向类B的带箭头虚线表示。
四、关联关系
关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标记。
五、聚合关系
聚合是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。比如计算机与CPU、公司与员工的关系等,比如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。
六、组合关系
组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如人和人的大脑。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,组合关系以实心菱形加实线箭头表示。
软件模式
软件模式: 设计模式、体系结构模式、分析模式、过程模式等。
体系结构模式
ANSIIEEEStd1471一200对体系结构的定义:一个系统的基本组织,表现为系统的组件、组件之间的相互关系、组件和环境之间的相互关系以及设计和进化的原则。
黑板模式
黑板模式是一种常用的架构模式,应用中的多种不同数据处理逻辑相互影响和协同来完成数据分析处理。
“就好像多位不同的专家在同一黑板上交流思想,每个专家都可以获得别的专家写在黑板上的信息,同时也可以用自己的分析去更新黑板上的信息,从而影响其它专家。”
数据库模式
发布—订阅模式 (难点)
1) 一个 delegate对象一次可以搭载多个方法(methods),而不是一次一个。当我们唤起一个搭载了多个方法(methods)的delegate,所有方法以其“被搭载到delegate对象的顺序”被依次唤起。
2) 一个delegate对象所搭载的方法(methods)并不需要属于同一个类别。一个delegate对象所搭载的所有方法(methods)必须具有相同的原型和形式。然而,这些方法(methods)可以即有static也有non-static,可以由一个或多个不同类别的成员组成。
3) 一个delegate type的声明在本质上是创建了一个新的subtype instance,该 subtype 派生自 .NET library framework 的 abstract base classes Delegate 或 MulticastDelegate,它们提供一组public methods用以询访delegate对象或其搭载的方法(methods) ,与函数指针不同,委托是面向对象、类型安全并且安全的。
为了实现P与S.P, S与S.P之间的解耦,我们需要定义两个接口文件:
ISubscribe.cs
namespace TJVictor.DesignPattern.SubscribePublish { //定义订阅事件 public delegate void SubscribeHandle(string str); //定义订阅接口 public interface ISubscribe { event SubscribeHandle SubscribeEvent; } }
IPublish.cs namespace TJVictor.DesignPattern.SubscribePublish { //定义发布事件 public delegate void PublishHandle(string str); //定义发布接口 public interface IPublish { event PublishHandle PublishEvent; void Notify(string str); } }
然后我们来设计订阅器。显然订阅器要实现双向解耦,就一定要继承上面两个接口,这也是我为什么用接口不用抽象类的原因(类是单继承)。
namespace TJVictor.DesignPattern.SubscribePublish
{
public class SubPubComponet : ISubscribe, IPublish
{
private string _subName;
public SubPubComponet(string subName)
{
this._subName = subName;
PublishEvent += new PublishHandle(Notify);
}
#region ISubscribe Members
event SubscribeHandle subscribeEvent;
event SubscribeHandle ISubscribe.SubscribeEvent
{
add { subscribeEvent += value; }
remove { subscribeEvent -= value; }
}
#endregion
#region IPublish Members
public PublishHandle PublishEvent;
event PublishHandle IPublish.PublishEvent
{
add { PublishEvent += value; }
remove { PublishEvent -= value; }
}
#endregion
public void Notify(string str)
{
if (subscribeEvent != null)
subscribeEvent.Invoke(string.Format("消息来源{0}:消息内容:{1}", _subName, str));
}
}
}
接下来是设计订阅者S。S类中使用了ISubscribe来与S.P进行解耦。代码如下:
namespace TJVictor.DesignPattern.SubscribePublish { public class Subscriber { private string _subscriberName; public Subscriber(string subscriberName) { this._subscriberName = subscriberName; } public ISubscribe AddSubscribe { set { value.SubscribeEvent += Show; } } public ISubscribe RemoveSubscribe { set { value.SubscribeEvent -= Show; } } private void Show(string str) { Console.WriteLine(string.Format("我是{0},我收到订阅的消息是:{1}", _subscriberName, str)); } } }
最后是发布者P,继承IPublish来对S.P发布消息通知。
namespace TJVictor.DesignPattern.SubscribePublish { public class Publisher:IPublish { private string _publisherName; public Publisher(string publisherName) { this._publisherName = publisherName; } private event PublishHandle PublishEvent; event PublishHandle IPublish.PublishEvent { add { PublishEvent += value; } remove { PublishEvent -= value; } } public void Notify(string str) { if (PublishEvent != null) PublishEvent.Invoke(string.Format("我是{0},我发布{1}消息", _publisherName, str)); } } }
至此,一个简单的订阅发布模式已经完成了。下面是调用代码及运行结果。调用代码模拟了图2中的订阅发布关系,大家可以从代码,运行结果和示例图三方面对照着看。
#region TJVictor.DesignPattern.SubscribePublish //新建两个订阅器 SubPubComponet subPubComponet1 = new SubPubComponet("订阅器1"); SubPubComponet subPubComponet2 = new SubPubComponet("订阅器2"); //新建两个发布者 IPublish publisher1 = new Publisher("TJVictor1"); IPublish publisher2 = new Publisher("TJVictor2"); //与订阅器关联 --> 订阅器 监听 发布者? publisher1.PublishEvent += subPubComponet1.PublishEvent; publisher1.PublishEvent += subPubComponet2.PublishEvent; publisher2.PublishEvent += subPubComponet2.PublishEvent; //新建两个订阅者 Subscriber s1 = new Subscriber("订阅人1"); Subscriber s2 = new Subscriber("订阅人2"); //进行订阅 s1.AddSubscribe = subPubComponet1; s1.AddSubscribe = subPubComponet2; s2.AddSubscribe = subPubComponet2; /***********************************************************/ //发布者发布消息 publisher1.Notify("博客1"); publisher2.Notify("博客2"); //发送结束符号 Console.WriteLine("".PadRight(50,'-')); //s1取消对订阅器2的订阅 s1.RemoveSubscribe = subPubComponet2; //发布者发布消息 publisher1.Notify("博客1"); publisher2.Notify("博客2"); //发送结束符号 Console.WriteLine("".PadRight(50, '-')); #endregion #region Console.ReadLine(); Console.ReadLine(); #endregion
模型-视图-控制(MVC:Model-View-Controller)
在满足对界面要求的同时,如何使软件的计算模型独立于界面的构成。
类似的结构模式
还有PAC(Presentation-Abstraction-Control)、Forward-Receiver、Publisher-Subscriber、各类可视化用户界面控件等。
分析模式
Reference: 软件分析模式的形式化研究, 钟琪, 西南师范大学
分析模式是更高层次的抽象。与测试模式不同,分析模式不反应实际的软件实现,而是体现行业业务过程的概念结构。
按用途可以划分为:Accountability, Observations and measurements, Observations for Corporate finance, Referring to Objects, Inventory, Planning, trading.
Example: Party in Accountability
过程模式
Reference: http://www.cnblogs.com/houleixx/archive/2009/10/20/software-engineering-process-model.html
软件过程是为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。
通常使用生命周期模型简洁地描述软件过程。生命周期模型规定了把生命周期划分成哪些阶段及各个阶段的执行顺序,因此,也称为过程模型。
常见的过程模型有瀑布模型、快速原型模型、增量模型、螺旋模型、喷泉模型等。
设计模式
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式的目的是提高代码的可重用性,让代码更容易被他人理解,并保证代码可靠性。它是代码编制真正实现工程化。
详见之后的篇章,加油。
知名博客:https://blog.csdn.net/lovelion/article/details/79602149
菜鸟教程:http://www.runoob.com/design-pattern/design-pattern-tutorial.html
End.