【笔记】大话设计模式-567
5 依赖倒转原则
5.1 定义
A 高层模块不应该依赖低层模块。两个都应该依赖抽象类。
B 抽象不应该依赖细节。细节应该依赖抽象。
说白了,就是要针对接口编程,而不是要对实现编程。要设计抽象类,具体实现在子类中完成。
举例来说:阿三的个人电脑PC里,如果CPU、内存、硬盘等都需要依赖具体的主板,主板一坏,他所有的部件就都没有用了。应该去耦合化,内存坏了不影响其他的功能部件。
高层模块依赖低层模块:
面向对象开发时,为了使得常用代码可以复用,一般都会将这些常用代码写成许多程序库,做新项目时,调用低层的函数就行了。就像如今ARM指令集这么火一样,就是将常用的80%指令集做了精简,在移动端调用时就非常方便、高效、低功耗,而剩余20%指令集,通过常用指令集拼接即可。
所以根据原则A,这就是依赖倒转了。
5.2 里氏代换原则
里氏代换: 子类型必须能够替换掉它们的父类型。
这就是C++中的多态,动态绑定时子类可以初始化父类,在使用父类类型的模块时,在无需修改的情况下,代码可以扩展。可以提升代码的可用性,使代码更灵活,下降耦合性。
子类拥有父类所有非private的行为和属性。
面向对象时,鸟和企鹅就不能设置为父类子类,因为鸟都能飞,而企鹅不能,所以企鹅不能继承鸟类。
5.3 总结
综合上面两个原则:
只有当子类可以替换掉父类,软件单位的功能不能受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
依赖倒转原则其实可以说是面向对象设计的标志,如果编写时考虑的都是如何针对抽象编程而不是针对细节的编程,即程序中所有的依赖关系都终止于抽象类或者接口,那就是面向对象的设计,反之,就是过程化的设计了。
6 装饰模式
6.1 定义
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
举个例子:比如阿三今天要去逛街,那么他要穿什么衣服呢?可以通过不同的装饰器选择不同的服装搭配。这就是装饰模式。
6.2 具体实现代码
#region 6 装饰模式
// 父类: Component类
// 父类为抽象类,方便扩展
abstract class Component
{
public abstract void Operation();
}
// 实体类: ConcreteComponent类,继承于Component
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("具体对象的操作");
}
}
// 装饰类: Decorator类,同样继承于Component
// 该类为抽象类,以便后面添加具体功能
abstract class Decorator: Component
{
// 父类为成员对象,接受实体类对象
protected Component component;
public void SetComponent(Component component)
{
this.component = component;
}
public override void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
// ConcreteDecoratorA 类,继承Decorator类
class ConcreteDecoratorA : Decorator
{
// 该类独有的功能
private string addedState;
public override void Operation()
{
// 运行原Component 的Operation()
base.Operation();
addedState = "New State";
Console.WriteLine("具体装饰对象A的操作");
}
}
// ConcreteDecoratorB 类,继承Decorator类
class ConcreteDecoratorB : Decorator
{
// 该类独有的功能
private void AddedBehavior()
{
}
public override void Operation()
{
// 运行原Component 的Operation()
base.Operation();
AddedBehavior();
Console.WriteLine("具体装饰对象B的操作");
}
}
// 客户端代码
static void Main(string[] args)
{
// 用 ConcreteComponent 实例化对象 c
ConcreteComponent c = new ConcreteComponent();
// 用装饰类A 实例化对象d1
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
// 用装饰类B 实例化对象d2
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
// 用对象d1 包装 c
d1.SetComponent(c);
// 用对象d2 包装 d1
d2.SetComponent(d1);
// 执行d2的 Operation()
d2.Operation();
Console.Read();
}
#endregion
结果如下:
就是利用SetComponent对对象进行包装,每个装饰对象的实现就和如何使用这个对象分开了,每个装饰对象只关心自己的功能,不需要关心被添加到对象链当中。
精髓就是实例化对象和装饰对象都继承于同一个父对象,这样子类都可以用父对象表示,就可以对实例化的父对象进行操作了,而且操作过程并不在对象的子类中。
变换思维:
如果没有共同的父对象Component
,那么装饰类就可以作为Decorator
的子类了,同理,装饰类Decorator
和其子类ConcreteDecorator
也可以合并成一个类。
6.3 总结
什么时候使用装饰模式?
当系统需要更新功能的时候,是向旧代码中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为。
如果加入到主类中,会增加主类的复杂度。
如果仅仅满足一些只在特定情况下才会执行的特殊行为需要,就可以用装饰模式,把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。
当执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。
优点
- 把类中的装饰功能从类中搬移去除,简化原有的类
- 有效地把类的核心职责和装饰功能区分开了,有效去除了相关类中重复的装饰逻辑
7 代理模式
7.1 定义
Proxy: 为其他对象提供一种代理,以控制对这个对象的访问
什么场景下需要使用代理模式呢?
**举个例子:**比如阿三想追求一个漂亮妹子,但是他又不好意思,想让大头帮忙送一些礼物或者信件,这时候,大头就是阿三的代理,大头的行为是代替阿三做出来的,实际表达人是阿三,但是与妹子的接触确实大头。
7.2 结构
与装饰模式有点像。首先定义一个共同的接口类Subject
,该类中定义一些方法,然后再实现两个类:真实实体类RealSubject
和代理类Proxy
,真实实体类具体实现接口,代理类中申明一个真实实体对象,并同样生成相关方法,只不过实现方式是通过调用成员变量——真实实体实例化对象的方法,并不是自己的方法。
#region 代理模式
// 定义主接口,使得使用RealSubject的地方都能使用Proxy
abstract class Subject
{
public abstract void Request();
}
// 定义Proxy所代表的真实实体
class RealSubject : Subject
{
public override void Request()
{
Console.WriteLine("真实的请求");
}
}
// 代理类,保存一个引用,使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体
class Proxy : Subject
{
RealSubject realSubject;
public override void Request()
{
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Request();
}
}
// 客户端代码
static void Main(string[] args)
{
Proxy proxy = new Proxy();
proxy.Request();
Console.Read();
}
运行结果:
7.3 应用场合
7.3.1 远程代理
为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。
**远程访问就是使用的代理模式:**比如WebService
在.Net
的应用时,在应用程序的项目中引入一个Web
引用,引用一个WebService
,此时会在项目中生成一个WebReference
的文件夹和一些文件,其实它们就是代理,使得客户端程序调用代理就可以解决远程访问问题。
7.3.2 虚拟代理
根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
浏览器用代理模式优化下载:比如阿三打开一个很大的HTML
网页时,里面可能有很多文字和图片,但是你可以很快打开它,此时你能看到的时所有的文字,图片却是一张一张下载后才能看到的,那些未打开的图片框,就是通过虚拟代理来代替了真实的图片,此时代理存储了真实图片的路径和尺寸。
7.3.3 安全代理
控制真实对象访问时的权限,一般用于对象应该有不同的访问权限的时候。
**举个例子:**比如阿三和大头都想访问吃播的视频,这时候,代理根据输入的对象时阿三和大头,反馈不同的结果。
7.3.4 智能指引
当调用真实的对象时,代理处理另外一些事。
- 如计算真实对象的引用次数,这样当没有对象引用时,可以自动释放;
- 当第一次引用一个持久对象时,将它装入内存;
- 在访问一个实际对象时,检查是否已经锁定它,确保其他对象不能改变它。