由于时间紧迫的关系,16、18-20、22-25、27-29章先跳过之后补上。
这里先学习17、21章单例模式和26章。
(17)适配器模式
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器主要解决的问题是需要的东西就在面前,但却不能使用,而短时间又无法改造它,于是我们就想办法适配它。系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式。由于C#、JAVA等语言都不支持多重继承(C++支持),也就是一个类只有一个父类,所以我们这里主要讲的是对象适配器。
Target(这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口)代码如下。
1 Class Target 2 { 3 Public virtual void Request() 4 { 5 Console.WriteLine(“普通请求”); 6 } 7 }
Adaptee (需要适配的类)代码如下:
1 class Adaptee 2 { 3 Public void SpecificRequest() 4 { 5 Console.WriteLine(“特殊请求!”); 6 } 7 }
Adapter(通过在内部包装一个Adaptee对象,把源接口转成目标接口)代码如下:
1 Class Adapter:Target 2 { 3 Private Adaptee adaptee=new Adaptee();//建立一个私有的Adaptee对象 4 Public override void Request() 5 { 6 adaptee.specificRequest();//这样就把可以把表面上调用Request()方法变成实际调用//SpecificRequest() 7 } 8 }
客户端代码如下:
1 static void Main(string[] args) 2 { 3 Target target=new Adapter(); 4 target.Request(); 5 Console.Read();//对客户端来说,调用的就是Target的Request() 6 }
两个类所做的事情相同或相似,但是具有不同的接口时要使用它。客户端代码可以统一调用同一接口,这样可以更简单、更直接、更紧凑。在双方都不太容易修改的时候再使用适配器模式适配。当然,适配器也不是用的越多越好,如果能在事前控制,尽量不要用,如果在无法改变原有设计和代码的情况时再运用。
(21)单例模式
- 单例模式
实例化与否的过程其实就和领导问你报告交了没有一样,只有你自己清楚交没交,所以实例化与否的过程应该由自己来判定,这是它自己的责任,别人只要使用它就可以了。所以现在的问题就是怎么样阻止他人不去用new实例化。可以直接把这个类的构造方法改成私有的,外部程序就不能用new来实例化它了。我们的目的是让这个类只实例化一次,写一个public方法,叫做GetInstance(),这个方法的目的就是返回一个类实例,而此方法中,去做是否有实例化的判断。如果没有实例化过,由调用private的构造方法new出这个实例,我们知道在同一个类的private方法是可以互相调用的。
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身复杂保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
Singleton类,定义一个GetInstance操作,允许客户访问它的唯一实例。GetInstance是一个静态方法,主要负责创建自己的唯一实例。
1 class Singleton 2 { 3 private static Singleton instance; 4 private Singleton()//构造方法让其private,这就堵死外界利用new创建此类实例 5 {} 6 public static Singleton GetInstance()//获得本类实例的唯一全局访问点 7 { 8 if(instance==null) //若实例不存在,则New一个新势力,否则返回已有实例 9 { 10 instance=new Singleton(); 11 } 12 return instance; 13 } 14 }
客户端代码
1 static void Main(string[] args) 2 { 3 Singleton s1=Singleton.GetInstance(); 4 Singleton s2=Singleton.GetInstance(); 5 if(s1==s2)//比较两次实例化后对象的结果是实例相同 6 { 7 Console.WriteLine("两个对象时相同的实例。"); 8 } 9 Console.Read(); 10 }
单例模式除了可以保证唯一的实例外,可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。
2.多线程时的单例
在多线程的程序中,多个线程同时访问Singleton类,调用GetInstance方法会有可能造成创建多个实例。可以给进程一把锁来处理lock。Lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区,直到该对象被是否。
双重锁定,我们不用让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。
1 class Singleton 2 { 3 private static Singleton instance; 4 private static readonly object syncRoot=new object();//程序运行时创建//一个静态只读的进程辅助对象 5 private Singleton(){} 6 public static Singleton GetInstance() 7 { 8 if(instance==null)//先判断实例是否存在,不存在再加锁处理 9 { 10 lock(syncRoot)//在同一个时刻加了锁的那部分程序只有一个线程可以///进入 11 { 12 if(instance==null) 13 { 14 instance=new Singleton(); 15 } 16 } 17 return instance; 18 } 19 } 20 } 21 22
为什么在里面又判断一次Instance==null,两个线程调用instance都可以通过第一重Instance,然后由于lock机制,这两个线程则有一个进入,另一个在外排队等候,如果没有第二重Instance是否为null的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,就违背了单例模式。
3.静态初始化
前面讲的方式属于懒汉式单例类,也就是要在第一重被引用时, 才将自己实例化。下面讲的是恶汉式单例类,也就是静态初始化方式,是在自己被加载时就将自己实例化。这种方法不需要显示地编写线程安全代码,既可解决多线 程环境下它是不安全的问题。它是C#公共语言运行库提供的一种静态初始化方法。我们看一下代码。
PS:1. readonly 字段可以在声明或构造函数中初始化。 因此,根据所使用的构造函数,readonly 字段可能具有不同的值。
2.static方法只能访问static成员变量
3.构造函数初始化静态变量,必须保证是静态构造函数
饿汉式单例类已经足够满足我们的需求了。
(26)享元模式
通常一些企业的需求和网站结构的相似度都很高,而且都不是那种高访问量的网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,这是造成服务器的大量资源浪费,当然更实际的启示就是钞票的浪费,如果整合到一个网站中,共享其相关的代码和数据,那么对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源,而对于代码,由于是一份实例,维护和扩展都更加容易。
享元技术(Flyweight),运用共享技术有效地支持大量细粒度的对象。
Flyweight类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
1 abstract class Flyweight 2 { 3 public abstract void Operation(int extrinsicstate); 4 }
ConcreteFlyweight是继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间。
1 class ConcreteFlyweight:Flyweight 2 { 3 public override void Operation(int extrinsicstate) 4 { 5 Console.WriteLine("具体Flyweight:"+extrinsicstate); 6 } 7 }
UsharedConcreteFlyweight是指那些不需要共享的Flyweight子类。因为Flyweight接口共享成为可能,但它并不强制共享。
1 class UnsharedConcreteFlyweight:Flyweight 2 { 3 public override void Operation(int extrinsicstate) 4 { 5 Console.WriteLine("不共享的具体Flyweight:"+extrinsicstate); 6 } 7 }
FlyweightFactory,是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
1 class FlyweightFactory 2 { 3 private Hashtable flyweights=new Hashtable(); 4 public flyweightFactory() 5 { 6 flyweight.Add("X",new ConcreteFlyweight()); 7 flyweight.Add("Y",new ConcreteFlyweight()); 8 flyweight.Add("Z",new ConcreteFlyweight()); 9 10 } 11 public Flyweight GetFlyweight(string key) 12 { 13 return ((Flyweight)flyweights[key]); 14 } 15 }
客户端代码
1 static void Main(string[] args) 2 { 3 int extrinsicstate=22;//代码外部状态 4 FlyweightFactory f=new FlyweightFactory(); 5 Flyweight fx=f.GetFlyweight("X"); 6 fx.Operation(--extrinsicstate); 7 8 Flyweight fy=f.GetFlyweight("Y); 9 fy.Operation(--extrinsicstate); 10 11 Flyweight fz=f.GetFlyweight("Z"); 12 fz.Operation(--extrinsicstate); 13 14 Flyweight uf=new UnsharedConcreteFlyweight(); 15 uf.Operation(--extrinsicstate); 16 17 Console.Read();
事实上,享元模式可以避免大量非常相思类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。
在什么时候考虑享元模式呢?如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
.NET中的字符串就是利用了Flyweight模式。如果每次创建字符串对象时都需要创建一个新的字符串对象的话,内存的开销会很大。当然本身维护享元列表就是一种资源耗费,所以需要当足够多的对象实例时才运用它。