摘自《C#的工程化实现及扩展》
工厂模式
把目标实例创建工作交给外部工厂完成,如果应用中需要工厂类型只有一个,而工厂只是一个new的替代品,面向对象就需要进一步抽象:工厂方法模式和抽象工厂模式。
工厂方法
类的实例化延迟到子类,同时增加一个抽象的工厂定义,解决一系列具有统一通用方法的实体工厂问题。
应用场景:
- 客户程序要隔离它与需要创建的具体类型间的耦合关系。
- 客户程序在开发中还无法预知生成环境实际要提供给客户程序创建的具体类型
- 将创建性工作隔离在客户程序外,客户程序只用执行自己的业务逻辑
- 如果目标对象可以统一抽象为某个抽象类型,但继承关系太复杂,层次性也复杂,可用工厂方法去解决。
四个角色:
- 抽象产品类型
- 具体产品类型
- 声明的抽象工厂类型(返回抽象产品类型)
- 重新定义创建工厂的具体工厂类型(返回具体产品类型)
解耦Concrete Factory和客户程序
上面的模式还是形成了间接的实体对象依赖关系,背离模式的初始意图。可以通过Assembler把客户程序需要的IFactory传给他。
static Assembler()
{
// 注册抽象类型需要使用的实体类型
// 实际的配置信息可以从外层机制获得,例如通过配置定义
dictionary.Add(typeof(IFactory), typeof(FactoryA));
}
class Client
{
private IFactory factory;
public Client(IFactory factory)
{
if(factory == NULL) throw new ArgumentNullException("factory");
this.factory = factory;
}
public void AnthorMethod()
{
IProduct product = factory.Create();
//……
}
}
在工程中往往要求Assembler知道更多的IFactory / Concrete Factory配置关系,且应用到Plug&&Play要求,新写的Concrete Factory类型最好保存在一个额外的程序集里,不涉及既有已部署好的应用,可以把Assembler需要加载的IFactory / Concrete Factory列表保存到配置文件里。
namespace MarvellousWorks.PracticalPattern.FactoryMethod.Classic
{
public class Assembler
{
// 配置节名称
private const string SectionName = "marvelousWorks.practicalPattern.factoryMethod.customFactories";
// IFactory在配置文件的键值
private const string FactoryTypeName = "IFactory";
private static Dictionary<Type, Type> dictionary = new Dictionary<Type, Type>();
}
static Assembler()
{
// 通过配置文件加载相关“抽象产品类型” / “实体产品类型”的映射关系
NameValueCollection collection = (NameValueCollection) ConfigurationSettings.GetConfig(SectionName);
for(int i = 0; i < collection.Count; i++){
string target = collection.GetKey(i);
string source = collection[i];
dictionary.Add(Type.GetType(target), Type.GetType(source));
}
}
//… …
}
namespace FactoryMethod.Test.Classic
{
//… …
}
class TestClient
{
public void Test()
{
IFactory factory = (new Assembler()).Create<IFactory>();
Client client = new Client(factory); // 注入IFactory
IProduct product = client.GetProduct(); // 获取IProduct
Assert.AreEqual<string>("A", product.Name); // 配置中选为FactoryA
}
}
批量工厂
经过一系列处理,只生产一件产品效率不高。需批量生产。
- 接受批量加工任务。
- 准备生产:加工对象步骤就new(),在之前需要编写很多访问配置文件,寻址抽象产品类型需要实例化的产品类型,增加其他例如记录日志,权限检查等。
- 配货:可选步骤,在此以实际生产过程做类比,需要生产不同类型产品。
- 指导生产的抽象类型:在保持IFactory时,生产某类的数量。
- Director的每个步骤称为一个决策:包括两个信息,数量和实际加工对象。
- 为批量的IProduct增加一个集合容器类型,也可直接使用既有集合类型(泛型集合累成),同时修改Concrete Factory加工方式,返回某个单独的IProduct为IProduct集合。容器结构如下:
定义批量工厂和产品类型容器:在经典工厂方法增加一个产品容器。
生产指导顾问——Director
指导客户调度不同实体工厂生产产品的指挥者,用迭代器组织每个decision。
类中含有IBatchFactory和quantity。
public abstract class DirectorBase
{
protected IList<DirecorBase> decisions = new List<DecisionBase>();
// 可将每个Director需要的Decision也定义在配置文件中
// 添加新的Decision都在后台完成,不用Assembler显示调用方法补充
protected void Insert(DecisionBase decision)
{
if((decision == null) || (decision.Factory == null) || (decision.Quantity < 0))
throw new ArgumentException("decision");
decisions.Add(decision);
}
}
泛型工厂方法
若工厂都是IFactory的某些子类,都有一个通用方法T create()。体系统一,不过需要增加一个强制类型转换,检查工作在运行态完成。
委托工厂类型
实现异步机制和组播,本质上是对具体执行方法的抽象,相当于Product角色。
public delegate int CalculateHandler(params int[] items)
class Calculator
{
public int Add(params int[] items)
{
int result = 0;
foreach(int item in items)
result += item;
return result;
}
}
public class CalculateHandlerFactory : IFactory<CalculateHandler>
{
public CalculateHandler Create()
{
return (new Calculator()).Add;
}
}
单例模式
保证一个类只有一个实例,同时提供用户一个访问的全局访问点。例如一个端口在同一时间只能有一个进程打开,数据库自增序号生成部分。
外部方式:用户程序在使用某些全局性的对象时,做一些try-create-store-use工作,没有就创建一个放在全局的位置。但外部方式很难要求所有相关类型都做唯一性检查。
内部方式:自己控制生成实例的数量,用户程序使用的都是这个现成的唯一实例。
在instance()访问接口,设置多层if,例如多线程注意共享资源同步。
虽然静态属性instance被定义为公共的,但它是只读的,一旦创建就不被修改,也不用做double check。
导致Singleton变质
- 不要实现ICloneable接口或继承相关子类,否则用户程序可以跳过已经隐藏的类构造函数。
public class BaseEntity : System.ICloneable
{
public object Clone() //对当前实例克隆
{
return this.MemberwiseClone();
}
}
public class Singleton : BaseEntity
{
// … …
}
- 严防序列化。远程访问需要把复杂的对象序列化后进行传递,但这事实上完成了Singleton对象的拷贝,所有不能声明SerialzableAttribute属性。
细颗粒度
例如某个线程是长时间运行的后台任务,本身存在很多模块和中间处理,但每个线程都希望有自己的线程内单独Singleton对象,其他线程也独立操作自己的线程内Singleton,线程级Singleton的实例总是为1*n个。
桌面应用细颗粒度Singleton问题
System.ThreadStaticAttribute可告诉CLR其中的静态唯一属性Instance仅在本线程内部静态,但lazy加载Singleton线程不安全。
public class Singleton
{
private Singleton() {}
[ThreadStatic] //说明每个Instance仅在当前线程内为静态
private static Singleton instance;
public static Singleton Instance
{
get
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
/// 每个线程需要执行的目标对象定义
/// 同时在它内部完成线程内部是否Singleton的情况
class Work
{
public static IList<int> log = new List<int>();
// 每个线程执行部分定义
public void procedure()
{
Singleton s1 = Singleton.Instance;
Singleton s2 = Singleton.Instance;
// 证明可以正常构造实例
Assert.IsNotNull(s1);
Assert.IsNotNull(s2);
// 验证当前线程执行体内部两次引用是否为同一个实例
Assert.AreEqual<int>(s1.GetHashCode(), s2.GetHashCode());
// 登记当前线程所使用的Singleton对象标识
Log.Add(s1.GetHashCode());
}
}
public class TestSingleton
{
private const int ThreadCount = 3;
public void Test()
{
Thread[] threads = new Thread[ThreadCount];
for(int i = 0;i < ThreadCount;i++)
{
ThreadStart work = new ThreadStart(new Work().Procedure);
thread[i] = new Thread(work);
}
foreach(Thread thread in threads) thread.Start();
for(int i = 0;i < ThreadCount -1;i++)
for(int j = i+1;j<ThreadCount;j++)
Assert.AreNotEqual<int>(Work.Log[i], Work.Log[j]);
}
}
Wed应用细颗粒度Singleton问题
Web Form应用的每个会话本地全局区域不是线程,而是自己的HttpContext,与上述的有所差别。
public class Singleton
{
//足够复杂的key值,与HttpContext其他内容相区别
private const string Key = "marvellousWorks.practical.singleton";
private Singleton() {}
public static Singleton Instance
{
get
{
// 基于HttpContext的Lazy实例化过程
Singleton instance = (Singleton)HttpContext.Current.Items[Key];
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
// Test与上述Work差距不大,不再赘述
更通用的细颗粒度Singleton
用在公共库或公共平台难以预料用在什么环境下,要保持唯一性,但常用的依赖倒置技巧不太适用。
实现一个web form + windows form 2 in 1的细颗粒度Singleton。
public class Singleton
{
//足够复杂的key值,与HttpContext其他内容相区别
private const string Key = "marvellousWorks.practical.singleton";
private Singleton() {}
[ThreadStatic]
private static Singleton instance;
public static Singleton Instance
{
get
{
if(GenericContext.CheckWhetherIsWeb()) //Web Form
{
// 基于HttpContext的Lazy实例化过程
Singleton instance = (Singleton)HttpContext.Current.Items[Key];
if (instance == null)
instance = new Singleton();
return instance;
}
else //非web form
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
}
持久不变的内容放在数据库、XML、报文,Singleton可以实现自动更新。
参数化的Singleton
硬编码形式或通过配置文件注入参数。
跨进程的Singleton
为实现一个线程安全用了double check或.NET.Framework内置机制,但运行不同服务器或跨越网络,很难控制instance==null的判断。
- 用Lazy方式若同时进入if(instance==null)判断,线程不安全
- double check方式无法通知对方lock,两边都是僵持的态度,满足一方持有部分争用过资源但又试图锁定对方争用资源容易形成死锁。
- 一个cluster,如果要做到负载均衡,很多时候要记录cluster每个节点的响应情况,按照配置好的比例分配负载,关键因素就是有个共享机制,有第三方。两边作为一个代理向本进程内部实体提供Singleton实体的引用,把构造工作抽象出去。
Client通过SingletonProxy传调用请求到Singleton类,则可按部就班地套用精简型或double check方式;如果是Cluster环境,Singleton可能运行在Controller上,也可以运行在某个Controller + Member机器上;如果仅仅是一个机器的多个进程间通信,则在某个并行进程内部,或自己在一个单独的进行。
半 Singleton方式:把需要操作的数据固定到一个持久层,即每个进程内部new出一个Singleton实例,效果一样,可以借助数据库数据复制或同步实现。
Singleton-N
Singleton定义了唯一的实例数量,根本目的是保证一些操作的一致性,比如更新计数器或某个应用级的某些全局性信息。但计算量过大,效率不高。所以做个扩展,让Singleton内部产生N个实例,N是固定的。若N个实例都处于忙碌状态,再次进行new()的时候,系统会说明“超出服务容量”。
经典lazy不适合并发环境;精简方式不适合构造多个对象;double check还可以。
根据单一职责原则,增加一个类workItemCollection。本身是个集合类型,最多存放某种类型的N个实例,每个实例通过状态标识自己处于忙碌还是闲暇状态,可告诉外界还有空位。
interface IWorkItem
{
Status status {get; set;}
void DeActivate(); //放弃使用
}
class WorkItemCollection<T>
where T: class, IWorkItem
{
protected int max;
protected IList<T> items = new List<T>();
public WorkItemCollection(int max) {this.max = max;}
public virtual T GetWorkItem()
{
if((items == null) || (items.Count == 0)) return null;
foreach(T item in items)
{
if(item.Status == Status.Free)
{
item.Status = Status.Busy;
return item;
}
}
return null;
}
public virtual void Add(T item)
{
if(item == null) throw new ArgumentNullException("item");
if(!CouldAddNewInstance) throw new OverflowException();
item.Status = Status.Free;
items.Add(item);
}
public virtual bool CouldAddNewInstance {
get {return (items.Count < max;) }
}
public class SingletonN :IWorkItem
{
private const int MaxInstance = 2;
private Status status = Status.Free;
public void DeActivate() {this.status = Status.Free; }
public Status Status
{
get {return this.status; }
set{this.status = value; }
}
}
private static WorkItemCollection<SingletonN> collection =
new WorkItemCollection<SingletonN>(MaxInstance);
public static SingletonN Instance
{
get
{
Singleton instance = collection.GetWorkItem();
if(instance == null)
{
if(!collection.CouldAddNewInstance )
return null;
else
{
instance = new SingletonN();
collection.Add(instance);
}
instance.Status = Status.Busy;
return instance;
}
}
}
方法间不存在明显耦合,可以把计算耗时部分提取出新的对象,本身不需要Singleton;但方法间本身难拆解或出于安全或封装需要,本身计算比较复杂,涉及调用共享争用资源的间隙很小,即可以用SingletonN。
用配置文件管理Singleton
Singleton-N的N可能需要按照服务器执行能力和业务负载进行调整;自动更新的Singleton若用内置时钟方式,最好将超时时间写到配置里;参数化Singleton本身也会将初始化参数写在配置元素集合里。
可在工程中采用或扩展.Net Framework自己的配置对象系统,关键的ConfigurationManager。
基于类型参数的Generic Singleton
可以允许外部机制额外构造其实例。
扩展了很多但本质还是生成一个实例,大型项目就得有规定的接口,要便于客户程序使用。
//定义一个非泛型的抽象基础
public interface ISingletonBase<T> : ISingleton
where T : ISingleton, new()
{
protected static T instance = new T();
public static T Instance{ get {return instance; }}
}
class SingletonA : SingletonBase<SingletonA> {}
class SingletonB : SingletonBase<SingletonB> {}
class SingletonC : SingletonBase<SingletonC> {}
由工厂类型协助 Singleton实例管理
通过工厂实现concrete singleton 类型和抽象ISingleton延迟加载,实现客户程序和concrete singleton的依赖倒置。
- 客户程序对于各concrete singleton类型通过程序集引用获得,把各个concrete singleton类型(做到internal可见)与相关的Singleton Factory定义在同一个程序集里。上述SingletonA 、SingletonB就是在,NET默认约定访问控制符——internal中被定义的。
- 通过在concrete singleton构造函数上增肌安全检查(权限检查属性、Role检查等),可以封闭外部客户程序的实例化过程。
class SingletonFactory
{
public ISingleton Create() {return SingletonA.Instance;}
}
//ISingleton singleton = (new SingletonFactory()).Create();
//Assert.IsNotNull(singleton);