0%

一、引言

  这个系列也是自己对设计模式的一些学习笔记,希望对一些初学设计模式的人有所帮助的,在上一个专题中介绍了单例模式,在这个专题中继续为大家介绍一个比较容易理解的模式——简单工厂模式。

二、简单工厂模式的介绍

  说到简单工厂,自然的第一个疑问当然就是什么是简单工厂模式了? 在现实生活中工厂是负责生产产品的,同样在设计模式中,简单工厂模式我们也可以理解为负责生产对象的一个类, 我们平常编程中,当使用”new”关键字创建一个对象时,此时该类就依赖与这个对象,也就是他们之间的耦合度高,当需求变化时,我们就不得不去修改此类的源码,此时我们可以运用面向对象(OO)的很重要的原则去解决这一的问题,该原则就是——封装改变,既然要封装改变,自然也就要找到改变的代码,然后把改变的代码用类来封装,这样的一种思路也就是我们简单工厂模式的实现方式了。下面通过一个现实生活中的例子来引出简单工厂模式。

  在外面打工的人,免不了要经常在外面吃饭,当然我们也可以自己在家做饭吃,但是自己做饭吃麻烦,因为又要自己买菜,然而,出去吃饭就完全没有这些麻烦的,我们只需要到餐馆点菜就可以了,买菜的事情就交给餐馆做就可以了,这里餐馆就充当简单工厂的角色,下面让我们看看现实生活中的例子用代码是怎样来表现的。

自己做饭的情况:

复制代码

///


/// 自己做饭的情况 /// 没有简单工厂之前,客户想吃什么菜只能自己炒的 ///

public class Customer
{ ///
/// 烧菜方法 ///

///
///
public static Food Cook(string type)
{
Food food = null; // 客户A说:我想吃西红柿炒蛋怎么办? // 客户B说:那你就自己烧啊 // 客户A说: 好吧,那就自己做吧
if (type.Equals(“西红柿炒蛋”))
{
food = new TomatoScrambledEggs();
} // 我又想吃土豆肉丝, 这个还是得自己做 // 我觉得自己做好累哦,如果能有人帮我做就好了?
else if (type.Equals(“土豆肉丝”))
{
food = new ShreddedPorkWithPotatoes();
} return food;
} static void Main(string[] args)
{ // 做西红柿炒蛋
Food food1 = Cook(“西红柿炒蛋”);
food1.Print();

        Food food2 \= Cook("土豆肉丝");
        food2.Print();

        Console.Read();
    }
} /// <summary>
/// 菜抽象类 /// </summary>
public abstract class Food
{ // 输出点了什么菜
    public abstract void Print();
} /// <summary>
/// 西红柿炒鸡蛋这道菜 /// </summary>
public class TomatoScrambledEggs : Food
{ public override void Print()
    {
        Console.WriteLine("一份西红柿炒蛋!");
    }
} /// <summary>
/// 土豆肉丝这道菜 /// </summary>
public class ShreddedPorkWithPotatoes : Food
{ public override void Print()
    {
        Console.WriteLine("一份土豆肉丝");
    }
}

复制代码

  自己做饭,如果我们想吃别的菜时,此时就需要去买这种菜和洗菜这些繁琐的操作,有了餐馆(也就是简单工厂)之后,我们就可以把这些操作交给餐馆去做,此时消费者(也就是我们)对菜(也就是具体对象)的依赖关系从直接变成的间接的,这样就是实现了面向对象的另一个原则——降低对象之间的耦合度,下面就具体看看有了餐馆之后的实现代码(即简单工厂的实现):

复制代码

///


/// 顾客充当客户端,负责调用简单工厂来生产对象 /// 即客户点菜,厨师(相当于简单工厂)负责烧菜(生产的对象) ///

class Customer
{ static void Main(string[] args)
{ // 客户想点一个西红柿炒蛋
Food food1 = FoodSimpleFactory.CreateFood(“西红柿炒蛋”);
food1.Print(); // 客户想点一个土豆肉丝
Food food2 = FoodSimpleFactory.CreateFood(“土豆肉丝”);
food2.Print();

        Console.Read();
    }
} /// <summary>
/// 菜抽象类 /// </summary>
public abstract class Food
{ // 输出点了什么菜
    public abstract void Print();
} /// <summary>
/// 西红柿炒鸡蛋这道菜 /// </summary>
public class TomatoScrambledEggs : Food
{ public override void Print()
    {
        Console.WriteLine("一份西红柿炒蛋!");
    }
} /// <summary>
/// 土豆肉丝这道菜 /// </summary>
public class ShreddedPorkWithPotatoes : Food
{ public override void Print()
    {
        Console.WriteLine("一份土豆肉丝");
    }
} /// <summary>
/// 简单工厂类, 负责 炒菜 /// </summary>
public class FoodSimpleFactory
{ public static Food CreateFood(string type)
    {
        Food food \= null; if (type.Equals("土豆肉丝"))
        {
            food\= new ShreddedPorkWithPotatoes();
        } else if (type.Equals("西红柿炒蛋"))
        {
            food\= new TomatoScrambledEggs();
        } return food;
    }
}

复制代码

三、优点与缺点

  看完简单工厂模式的实现之后,你和你的小伙伴们肯定会有这样的疑惑(因为我学习的时候也有)——这样我们只是把变化移到了工厂类中罢了,好像没有变化的问题,因为如果客户想吃其他菜时,此时我们还是需要修改工厂类中的方法(也就是多加case语句,没应用简单工厂模式之前,修改的是客户类)。我首先要说:你和你的小伙伴很对,这个就是简单工厂模式的缺点所在(这个缺点后面介绍的工厂方法可以很好地解决),然而,简单工厂模式与之前的实现也有它的优点:

  • 简单工厂模式解决了客户端直接依赖于具体对象的问题,客户端可以消除直接创建对象的责任,而仅仅是消费产品。简单工厂模式实现了对责任的分割。
  • 简单工厂模式也起到了代码复用的作用,因为之前的实现(自己做饭的情况)中,换了一个人同样要去在自己的类中实现做菜的方法,然后有了简单工厂之后,去餐馆吃饭的所有人都不用那么麻烦了,只需要负责消费就可以了。此时简单工厂的烧菜方法就让所有客户共用了。(同时这点也是简单工厂方法的缺点——因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响,也没什么不好理解的,就如事物都有两面性一样道理

虽然上面已经介绍了简单工厂模式的缺点,下面还是总结下简单工厂模式的缺点:

  • 工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响(通俗地意思就是:一旦餐馆没饭或者关门了,很多不愿意做饭的人就没饭吃了)
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,这样就会造成工厂逻辑过于复杂。

了解了简单工厂模式之后的优缺点之后,我们之后就可以知道简单工厂的应用场景了:

  • 当工厂类负责创建的对象比较少时可以考虑使用简单工厂模式()
  • 客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时可以考虑使用简单工厂模式

四、简单工厂模式UML图

简单工厂模式又叫静态方法模式(因为工厂类都定义了一个静态方法),由一个工厂类根据传入的参数决定创建出哪一种产品类的实例(通俗点表达:通过客户下的订单来负责烧那种菜)。简单工厂模式的UML图如下:

五、.NET中简单工厂模式的实现

  介绍完了简单工厂模式之后,我学习的时候就像:.NET类库中是否有实现了简单工厂模式的类呢?后面确实有,.NET中System.Text.Encoding类就实现了简单工厂模式,该类中的**GetEncoding(int codepage)就是工厂方法,**具体的代码可以通过Reflector反编译工具进行查看,下面我也贴出该方法中部分代码:

public static Encoding GetEncoding(int codepage)
{
Encoding unicode = null; if (encodings != null)
{
unicode = (Encoding) encodings[codepage];
} if (unicode == null)
{ object obj2; bool lockTaken = false; try {
Monitor.Enter(obj2 = InternalSyncObject, ref lockTaken); if (encodings == null)
{
encodings = new Hashtable();
}
unicode = (Encoding) encodings[codepage]; if (unicode != null)
{ return unicode;
} switch (codepage)
{ case 0:
unicode = Default; break; case 1: case 2: case 3: case 0x2a: throw new ArgumentException(Environment.GetResourceString(“Argument_CodepageNotSupported”, new object[] { codepage }), “codepage”); case 0x4b0:
unicode = Unicode; break; case 0x4b1:
unicode = BigEndianUnicode; break; case 0x6faf:
unicode = Latin1; break; case 0xfde9:
unicode = UTF8; break; case 0x4e4:
unicode = new SBCSCodePageEncoding(codepage); break; case 0x4e9f:
unicode = ASCII; break; default:
unicode = GetEncodingCodePage(codepage); if (unicode == null)
{
unicode = GetEncodingRare(codepage);
} break;
}
encodings.Add(codepage, unicode); return unicode;

    }

}

View Code

.NET 中Encoding的UML图为:

Encoding类中实现的简单工厂模式是简单工厂模式的一种演变,此时简单工厂类由抽象产品角色扮演,然而.NET中Encoding类是如何解决简单工厂模式中存在的问题的呢(即如果新添加一种编码怎么办)?在GetEncoding方法里的switch函数有如下代码:

复制代码

switch (codepage)
{
……. default:
unicode = GetEncodingCodePage(codepage); if (unicode == null)
{
unicode = GetEncodingRare(codepage); //当编码很少见时
} break;
……
}

复制代码

  在GetEncodingRare方法里有一些不常用编码的实例化代码,微软正式通过这个方法来解决新增加一种编码的问题。(其实也就是列出所有可能的编码情况),微软之所以以这样的方式来解决这个问题,可能是由于现在编码已经稳定了,添加新编码的可能性比较低,所以在.NET 4.5仍然未改动这部分代码。

六、总结

到这里,简单工厂模式的介绍都到这里了,后面将介绍工厂方法模式来解决简单工厂模式中存在的问题。

本专题中的全部源码:简单工厂模式源码

   前面主题介绍的状态模式是对某个对象状态的抽象,而本文要介绍的策略模式也就是对策略进行抽象,策略的意思就是方法,所以也就是对方法的抽象,下面具体分享下我对策略模式的理解。

2.1 策略模式的定义

  在现实生活中,策略模式的例子也非常常见,例如,中国的所得税,分为企业所得税、外商投资企业或外商企业所得税和个人所得税,针对于这3种所得税,针对每种,所计算的方式不同,个人所得税有个人所得税的计算方式,而企业所得税有其对应计算方式。如果不采用策略模式来实现这样一个需求的话,可能我们会定义一个所得税类,该类有一个属性来标识所得税的类型,并且有一个计算税收的CalculateTax()方法,在该方法体内需要对税收类型进行判断,通过if-else语句来针对不同的税收类型来计算其所得税。这样的实现确实可以解决这个场景吗,但是这样的设计不利于扩展,如果系统后期需要增加一种所得税时,此时不得不回去修改CalculateTax方法来多添加一个判断语句,这样明白违背了“开放——封闭”原则。此时,我们可以考虑使用策略模式来解决这个问题,既然税收方法是这个场景中的变化部分,此时自然可以想到对税收方法进行抽象。具体的实现代码见2.3部分。

  前面介绍了策略模式用来解决的问题,下面具体给出策略的定义。策略模式是针对一组算法,将每个算法封装到具有公共接口的独立的类中,从而使它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

2.2 策略模式的结构

  策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象负责。策略模式通常把一系列的算法包装到一系列的策略类里面。用一句话慨括策略模式就是——“将每个算法封装到不同的策略类中,使得它们可以互换”。

  下面是策略模式的结构图:

  

  该模式涉及到三个角色:

  • 环境角色(Context):持有一个Strategy类的引用
  • 抽象策略角色(Strategy):这是一个抽象角色,通常由一个接口或抽象类来实现。此角色给出所有具体策略类所需实现的接口。
  • 具体策略角色(ConcreteStrategy):包装了相关算法或行为。

2.3 策略模式的实现

  下面就以所得税的例子来实现下策略模式,具体实现代码如下所示:

复制代码

1 namespace StrategyPattern 2 {
3 // 所得税计算策略
4 public interface ITaxStragety 5 {
6 double CalculateTax(double income); 7 }
8
9 // 个人所得税
10 public class PersonalTaxStrategy : ITaxStragety 11 { 12 public double CalculateTax(double income) 13 { 14 return income * 0.12; 15 } 16 } 17
18 // 企业所得税
19 public class EnterpriseTaxStrategy : ITaxStragety 20 { 21 public double CalculateTax(double income) 22 { 23 return (income - 3500) > 0 ? (income - 3500) * 0.045 : 0.0; 24 } 25 } 26
27 public class InterestOperation 28 { 29 private ITaxStragety m_strategy; 30 public InterestOperation(ITaxStragety strategy) 31 { 32 this.m_strategy = strategy; 33 } 34
35 public double GetTax(double income) 36 { 37 return m_strategy.CalculateTax(income); 38 } 39 } 40
41 class App 42 { 43 static void Main(string[] args) 44 { 45 // 个人所得税方式
46 InterestOperation operation = new InterestOperation(new PersonalTaxStrategy()); 47 Console.WriteLine(“个人支付的税为:{0}”, operation.GetTax(5000.00)); 48
49 // 企业所得税
50 operation = new InterestOperation(new EnterpriseTaxStrategy()); 51 Console.WriteLine(“企业支付的税为:{0}”, operation.GetTax(50000.00)); 52
53 Console.Read(); 54 } 55 } 56 }  

复制代码

   在.NET Framework中也不乏策略模式的应用例子。例如,在.NET中,为集合类型ArrayList和List提供的排序功能,其中实现就利用了策略模式,定义了IComparer接口来对比较算法进行封装,实现IComparer接口的类可以是顺序,或逆序地比较两个对象的大小,具体.NET中的实现可以使用反编译工具查看List.Sort(IComparer)的实现。其中List就是承担着环境角色,而IComparer接口承担着抽象策略角色,具体的策略角色就是实现了IComparer接口的类,List类本身实现了存在实现了该接口的类,我们可以自定义继承与该接口的具体策略类。

   在下面的情况下可以考虑使用策略模式:

  • 一个系统需要动态地在几种算法中选择一种的情况下。那么这些算法可以包装到一个个具体的算法类里面,并为这些具体的算法类提供一个统一的接口。
  • 如果一个对象有很多的行为,如果不使用合适的模式,这些行为就只好使用多重的if-else语句来实现,此时,可以使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象涉及的概念。

   策略模式的主要优点有:

  • 策略类之间可以自由切换。由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码。
  • 避免使用多重条件选择语句,充分体现面向对象设计思想。

  策略模式的主要缺点有:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这点可以考虑使用IOC容器和依赖注入的方式来解决,关于IOC容器和依赖注入(Dependency Inject)的文章可以参考:IoC 容器和Dependency Injection 模式
  • 策略模式会造成很多的策略类。

  到这里,策略模式的介绍就结束了,策略模式主要是对方法的封装,把一系列方法封装到一系列的策略类中,从而使不同的策略类可以自由切换和避免在系统使用多重条件选择语句来选择针对不同情况来选择不同的方法。在下一章将会大家介绍责任链模式。

  在现实生活中,有很多请求并不是一个人说了就算的,例如面试时的工资,低于1万的薪水可能技术经理就可以决定了,但是1万~1万5的薪水可能技术经理就没这个权利批准,可能就需要请求技术总监的批准,所以在面试的完后,经常会有面试官说,你这个薪水我这边觉得你这技术可以拿这个薪水的,但是还需要技术总监的批准等的话。这个例子也就诠释了本文要介绍的内容。生活中的这个例子真是应用了责任链模式。

二、责任链模式介绍

2.1 责任链模式的定义

  从生活中的例子可以发现,某个请求可能需要几个人的审批,即使技术经理审批完了,还需要上一级的审批。这样的例子,还有公司中的请假,少于3天的,直属Leader就可以批准,3天到7天之内就需要项目经理批准,多余7天的就需要技术总监的批准了。介绍了这么多生活中责任链模式的例子的,下面具体给出面向对象中责任链模式的定义。

  责任链模式指的是——某个请求需要多个对象进行处理,从而避免请求的发送者和接收之间的耦合关系。将这些对象连成一条链子,并沿着这条链子传递该请求,直到有对象处理它为止。

2.2 责任链模式的结构图

  从责任链模式的定义可以发现,责任链模式涉及的对象只有处理者角色,但由于有多个处理者,它们具有共同的处理请求的方法,所以这里抽象出一个抽象处理者角色进行代码复用。这样分析下来,责任链模式的结构图也就不言而喻了,具体结构图如下所示。

  主要涉及两个角色:

  • 抽象处理者角色(Handler):定义出一个处理请求的接口。这个接口通常由接口或抽象类来实现。
  • 具体处理者角色(ConcreteHandler):具体处理者接受到请求后,可以选择将该请求处理掉,或者将请求传给下一个处理者。因此,每个具体处理者需要保存下一个处理者的引用,以便把请求传递下去。

2.3 责任链模式的实现

  有了上面的介绍,下面以公司采购东西为例子来实现责任链模式。公司规定,采购架构总价在1万之内,经理级别的人批准即可,总价大于1万小于2万5的则还需要副总进行批准,总价大于2万5小于10万的需要还需要总经理批准,而大于总价大于10万的则需要组织一个会议进行讨论。对于这样一个需求,最直观的方法就是设计一个方法,参数是采购的总价,然后在这个方法内对价格进行调整判断,然后针对不同的条件交给不同级别的人去处理,这样确实可以解决问题,但这样一来,我们就需要多重if-else语句来进行判断,但当加入一个新的条件范围时,我们又不得不去修改原来设计的方法来再添加一个条件判断,这样的设计显然违背了“开-闭”原则。这时候,可以采用责任链模式来解决这样的问题。具体实现代码如下所示。

复制代码

namespace ChainofResponsibility
{ // 采购请求
public class PurchaseRequest
{ // 金额
public double Amount { get; set; } // 产品名字
public string ProductName { get; set; } public PurchaseRequest(double amount, string productName)
{
Amount = amount;
ProductName = productName;
}
} // 审批人,Handler
public abstract class Approver
{ public Approver NextApprover { get; set; } public string Name { get; set; } public Approver(string name)
{ this.Name = name;
} public abstract void ProcessRequest(PurchaseRequest request);
} // ConcreteHandler
public class Manager : Approver
{ public Manager(string name)
: base(name)
{ } public override void ProcessRequest(PurchaseRequest request)
{ if (request.Amount < 10000.0)
{
Console.WriteLine(“{0}-{1} approved the request of purshing {2}”, this, Name, request.ProductName);
} else if (NextApprover != null)
{
NextApprover.ProcessRequest(request);
}
}
} // ConcreteHandler,副总
public class VicePresident : Approver
{ public VicePresident(string name)
: base(name)
{
} public override void ProcessRequest(PurchaseRequest request)
{ if (request.Amount < 25000.0)
{
Console.WriteLine(“{0}-{1} approved the request of purshing {2}”, this, Name, request.ProductName);
} else if (NextApprover != null)
{
NextApprover.ProcessRequest(request);
}
}
} // ConcreteHandler,总经理
public class President :Approver
{ public President(string name)
: base(name)
{ } public override void ProcessRequest(PurchaseRequest request)
{ if (request.Amount < 100000.0)
{
Console.WriteLine(“{0}-{1} approved the request of purshing {2}”, this, Name, request.ProductName);
} else {
Console.WriteLine(“Request需要组织一个会议讨论”);
}
}
} class Program
{ static void Main(string[] args)
{
PurchaseRequest requestTelphone = new PurchaseRequest(4000.0, “Telphone”);
PurchaseRequest requestSoftware = new PurchaseRequest(10000.0, “Visual Studio”);
PurchaseRequest requestComputers = new PurchaseRequest(40000.0, “Computers”);

        Approver manager \= new Manager("LearningHard");
        Approver Vp \= new VicePresident("Tony");
        Approver Pre \= new President("BossTom"); // 设置责任链
        manager.NextApprover = Vp;
        Vp.NextApprover \= Pre; // 处理请求

manager.ProcessRequest(requestTelphone);
manager.ProcessRequest(requestSoftware);
manager.ProcessRequest(requestComputers);
Console.ReadLine();
}
}
}

复制代码

  既然,原来的设计会因为价格条件范围的变化而导致不利于扩展,根据“封装变化”的原则,此时我们想的自然是能不能把价格范围细化到不同的类中呢?因为每个价格范围都决定某个批准者,这里就联想到创建多个批准类,这样每个类中只需要针对他自己这个范围的价格判断。这样也就是责任链的最后实现方式了,具体的运行结果如下图所示。

  在以下场景中可以考虑使用责任链模式:

  • 一个系统的审批需要多个对象才能完成处理的情况下,例如请假系统等。
  • 代码中存在多个if-else语句的情况下,此时可以考虑使用责任链模式来对代码进行重构。

  责任链模式的优点不言而喻,主要有以下点:

  • 降低了请求的发送者和接收者之间的耦合。
  • 把多个条件判定分散到各个处理类中,使得代码更加清晰,责任更加明确。

  责任链模式也具有一定的缺点,如:

  • 在找到正确的处理对象之前,所有的条件判定都要执行一遍,当责任链过长时,可能会引起性能的问题
  • 可能导致某个请求不被处理。

  责任链降低了请求端和接收端之间的耦合,使多个对象都有机会处理某个请求。如考试中作弊传纸条,泡妞传情书一般。在下一章将继续分享访问者模式。

  在上一篇博文中分享了责任链模式,责任链模式主要应用在系统中的某些功能需要多个对象参与才能完成的场景。在这篇博文中,我将为大家分享我对访问者模式的理解。

2.1 访问者模式的定义

   访问者模式是封装一些施加于某种数据结构之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保存不变。访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操作之间的耦合度降低,使得操作集合可以相对自由地改变。

  数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。

2.2 访问者模式的结构图

   从上面描述可知,访问者模式是用来封装某种数据结构中的方法。具体封装过程是:每个元素接受一个访问者的调用,每个元素的Accept方法接受访问者对象作为参数传入,访问者对象则反过来调用元素对象的操作。具体的访问者模式结构图如下所示。

  这里需要明确一点:访问者模式中具体访问者的数目和具体节点的数目没有任何关系。从访问者的结构图可以看出,访问者模式涉及以下几类角色。

  • 抽象访问者角色(Vistor):声明一个活多个访问操作,使得所有具体访问者必须实现的接口。
  • 具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口。
  • 抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数。
  • 具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。
  • 结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。

2.3 访问者模式的实现

   在讲诉访问者模式的实现时,我想先不用访问者模式的方式来实现某个场景。具体场景是——现在我想遍历每个元素对象,然后调用每个元素对象的Print方法来打印该元素对象的信息。如果此时不采用访问者模式的话,实现这个场景再简单不过了,具体实现代码如下所示:

复制代码

1 namespace DonotUsevistorPattern 2 {
3 // 抽象元素角色
4 public abstract class Element 5 {
6 public abstract void Print(); 7 }
8
9 // 具体元素A
10 public class ElementA : Element 11 { 12 public override void Print() 13 { 14 Console.WriteLine(“我是元素A”); 15 } 16 } 17
18 // 具体元素B
19 public class ElementB : Element 20 { 21 public override void Print() 22 { 23 Console.WriteLine(“我是元素B”); 24 } 25 } 26
27 // 对象结构
28 public class ObjectStructure 29 { 30 private ArrayList elements = new ArrayList(); 31
32 public ArrayList Elements 33 { 34 get { return elements; } 35 } 36
37 public ObjectStructure() 38 { 39 Random ran = new Random(); 40 for (int i = 0; i < 6; i++) 41 { 42 int ranNum = ran.Next(10); 43 if (ranNum > 5) 44 { 45 elements.Add(new ElementA()); 46 } 47 else
48 { 49 elements.Add(new ElementB()); 50 } 51 } 52 } 53 } 54
55 class Program 56 { 57 static void Main(string[] args) 58 { 59 ObjectStructure objectStructure = new ObjectStructure(); 60 // 遍历对象结构中的对象集合,访问每个元素的Print方法打印元素信息
61 foreach (Element e in objectStructure.Elements) 62 { 63 e.Print(); 64 } 65
66 Console.Read(); 67 } 68 } 69 }

复制代码

  上面代码很准确了解决了我们刚才提出的场景,但是需求在时刻变化的,如果此时,我除了想打印元素的信息外,还想打印出元素被访问的时间,此时我们就不得不去修改每个元素的Print方法,再加入相对应的输入访问时间的输出信息。这样的设计显然不符合“开-闭”原则,即某个方法操作的改变,会使得必须去更改每个元素类。既然,这里变化的点是操作的改变,而每个元素的数据结构是不变的。所以此时就思考——能不能把操作于元素的操作和元素本身的数据结构分开呢?解开这两者的耦合度,这样如果是操作发现变化时,就不需要去更改元素本身了,但是如果是元素数据结构发现变化,例如,添加了某个字段,这样就不得不去修改元素类了。此时,我们可以使用访问者模式来解决这个问题,即把作用于具体元素的操作由访问者对象来调用。具体的实现代码如下所示:

复制代码

1 namespace VistorPattern 2 {
3 // 抽象元素角色
4 public abstract class Element 5 {
6 public abstract void Accept(IVistor vistor); 7 public abstract void Print(); 8 }
9
10 // 具体元素A
11 public class ElementA :Element 12 {
13 public override void Accept(IVistor vistor) 14 {
15 // 调用访问者visit方法
16 vistor.Visit(this);
17 }
18 public override void Print() 19 {
20 Console.WriteLine(“我是元素A”);
21 }
22 }
23
24 // 具体元素B
25 public class ElementB :Element 26 {
27 public override void Accept(IVistor vistor) 28 {
29 vistor.Visit(this);
30 }
31 public override void Print() 32 {
33 Console.WriteLine(“我是元素B”);
34 }
35 }
36
37 // 抽象访问者
38 public interface IVistor 39 {
40 void Visit(ElementA a); 41 void Visit(ElementB b); 42 }
43
44 // 具体访问者
45 public class ConcreteVistor :IVistor 46 {
47 // visit方法而是再去调用元素的Accept方法
48 public void Visit(ElementA a) 49 {
50 a.Print();
51 }
52 public void Visit(ElementB b) 53 {
54 b.Print();
55 }
56 }
57
58 // 对象结构
59 public class ObjectStructure 60 {
61 private ArrayList elements = new ArrayList(); 62
63 public ArrayList Elements 64 {
65 get { return elements; } 66 }
67
68 public ObjectStructure() 69 {
70 Random ran = new Random(); 71 for (int i = 0; i < 6; i++)
72 {
73 int ranNum = ran.Next(10);
74 if (ranNum > 5)
75 {
76 elements.Add(new ElementA()); 77 }
78 else
79 {
80 elements.Add(new ElementB()); 81 }
82 }
83 }
84 }
85
86 class Program 87 {
88 static void Main(string[] args)
89 {
90 ObjectStructure objectStructure = new ObjectStructure(); 91 foreach (Element e in objectStructure.Elements) 92 {
93 // 每个元素接受访问者访问
94 e.Accept(new ConcreteVistor()); 95 }
96
97 Console.Read();
98 }
99 } 100 }

复制代码

  从上面代码可知,使用访问者模式实现上面场景后,元素Print方法的访问封装到了访问者对象中了(我觉得可以把Print方法封装到具体访问者对象中。),此时客户端与元素的Print方法就隔离开了。此时,如果需要添加打印访问时间的需求时,此时只需要再添加一个具体的访问者类即可。此时就不需要去修改元素中的Print()方法了。

   每个设计模式都有其应当使用的情况,那让我们看看访问者模式具体应用场景。如果遇到以下场景,此时我们可以考虑使用访问者模式。

  • 如果系统有比较稳定的数据结构,而又有易于变化的算法时,此时可以考虑使用访问者模式。因为访问者模式使得算法操作的添加比较容易。
  • 如果一组类中,存在着相似的操作,为了避免出现大量重复的代码,可以考虑把重复的操作封装到访问者中。(当然也可以考虑使用抽象类了)
  • 如果一个对象存在着一些与本身对象不相干,或关系比较弱的操作时,为了避免操作污染这个对象,则可以考虑把这些操作封装到访问者对象中。

   访问者模式具有以下优点:

  • 访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。
  • 访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与”中介者模式”。
  • 访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。

  访问者模式也有如下的缺点:

  • 增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。

  访问者模式是用来封装一些施加于某种数据结构之上的操作。它使得可以在不改变元素本身的前提下增加作用于这些元素的新操作,访问者模式的目的是把操作从数据结构中分离出来。

   在上一篇博文分享了访问者模式,访问者模式的实现是把作用于某种数据结构上的操作封装到访问者中,使得操作和数据结构隔离。而今天要介绍的备忘者模式与命令模式有点相似,不同的是,命令模式保存的是发起人的具体命令(命令对应的是行为),而备忘录模式保存的是发起人的状态(而状态对应的数据结构,如属性)。下面具体来看看备忘录模式。

2.1 备忘录模式的定义

   从字面意思就可以明白,备忘录模式就是对某个类的状态进行保存下来,等到需要恢复的时候,可以从备忘录中进行恢复。生活中这样的例子经常看到,如备忘电话通讯录,备份操作操作系统,备份数据库等。

  备忘录模式的具体定义是:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。

2.2 备忘录模式的结构图

   介绍完备忘录模式的定义之后,下面具体看看备忘录模式的结构图:

  备忘录模式中主要有三类角色:

  • 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。
  • 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。
  • 管理者角色:负责保存备忘录对象。

2.3 备忘录模式的实现

   下面以备份手机通讯录为例子来实现了备忘录模式,具体的实现代码如下所示:

复制代码

// 联系人
public class ContactPerson
{ public string Name { get; set; } public string MobileNum { get; set; }
} // 发起人
public class MobileOwner
{ // 发起人需要保存的内部状态
public List ContactPersons { get; set; } public MobileOwner(List persons)
{
ContactPersons = persons;
} // 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{ // 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List(this.ContactPersons));
} // 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{ // 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.contactPersonBack;
} public void Show()
{
Console.WriteLine(“联系人列表中有{0}个人,他们是:”, ContactPersons.Count); foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine(“姓名: {0} 号码为: {1}”, p.Name, p.MobileNum);
}
}
} // 备忘录
public class ContactMemento
{ // 保存发起人的内部状态
public List contactPersonBack; public ContactMemento(List persons)
{
contactPersonBack = persons;
}
} // 管理角色
public class Caretaker
{ public ContactMemento ContactM { get; set; }
} class Program
{ static void Main(string[] args)
{
List persons = new List()
{ new ContactPerson() { Name= “Learning Hard”, MobileNum = “123445”}, new ContactPerson() { Name = “Tony”, MobileNum = “234565”}, new ContactPerson() { Name = “Jock”, MobileNum = “231455”}
};
MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show(); // 创建备忘录并保存备忘录对象
Caretaker caretaker = new Caretaker();
caretaker.ContactM = mobileOwner.CreateMemento(); // 更改发起人联系人列表
Console.WriteLine(“-—移除最后一个联系人——–”);
mobileOwner.ContactPersons.RemoveAt(2);
mobileOwner.Show(); // 恢复到原始状态
Console.WriteLine(“-——恢复联系人列表——“);
mobileOwner.RestoreMemento(caretaker.ContactM);
mobileOwner.Show();

        Console.Read();
    }
}

复制代码

  具体的运行结果如下图所示:

  从上图可以看出,刚开始通讯录中有3个联系人,然后移除以后一个后变成2个联系人了,最后恢复原来的联系人列表后,联系人列表中又恢复为3个联系人了。

  上面代码只是保存了一个还原点,即备忘录中只保存了3个联系人的数据,但是,如果想备份多个还原点怎么办呢?即恢复到3个人后,又想恢复到前面2个人的状态,这时候可能你会想,这样没必要啊,到时候在删除不就好了。但是如果在实际应用中,可能我们发了很多时间去创建通讯录中只有2个联系人的状态,恢复到3个人的状态后,发现这个状态时错误的,还是原来2个人的状态是正确的,难道我们又去花之前的那么多时间去重复操作吗?这显然不合理,如果就思考,能不能保存多个还原点呢?保存多个还原点其实很简单,只需要保存多个备忘录对象就可以了。具体实现代码如下所示:

复制代码

namespace MultipleMementoPattern
{ // 联系人
public class ContactPerson
{ public string Name { get; set; } public string MobileNum { get; set; }
} // 发起人
public class MobileOwner
{ public List ContactPersons { get; set; } public MobileOwner(List persons)
{
ContactPersons = persons;
} // 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{ // 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List(this.ContactPersons));
} // 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{ if (memento != null)
{ // 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.ContactPersonBack;
}
} public void Show()
{
Console.WriteLine(“联系人列表中有{0}个人,他们是:”, ContactPersons.Count); foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine(“姓名: {0} 号码为: {1}”, p.Name, p.MobileNum);
}
}
} // 备忘录
public class ContactMemento
{ public List ContactPersonBack {get;set;} public ContactMemento(List persons)
{
ContactPersonBack = persons;
}
} // 管理角色
public class Caretaker
{ // 使用多个备忘录来存储多个备份点
public Dictionary<string, ContactMemento> ContactMementoDic { get; set; } public Caretaker()
{
ContactMementoDic = new Dictionary<string, ContactMemento>();
}
} class Program
{ static void Main(string[] args)
{
List persons = new List()
{ new ContactPerson() { Name= “Learning Hard”, MobileNum = “123445”}, new ContactPerson() { Name = “Tony”, MobileNum = “234565”}, new ContactPerson() { Name = “Jock”, MobileNum = “231455”}
};

        MobileOwner mobileOwner \= new MobileOwner(persons);
        mobileOwner.Show(); // 创建备忘录并保存备忘录对象
        Caretaker caretaker = new Caretaker();
        caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 更改发起人联系人列表
        Console.WriteLine("\----移除最后一个联系人--------");
        mobileOwner.ContactPersons.RemoveAt(2);
        mobileOwner.Show(); // 创建第二个备份
        Thread.Sleep(1000);
        caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 恢复到原始状态
        Console.WriteLine("\-------恢复联系人列表,请从以下列表选择恢复的日期------"); var keyCollection = caretaker.ContactMementoDic.Keys; foreach (string k in keyCollection)
        {
            Console.WriteLine("Key = {0}", k);
        } while (true)
        {
            Console.Write("请输入数字,按窗口的关闭键退出:"); int index = -1; try {
                index \= Int32.Parse(Console.ReadLine());
            } catch {
                Console.WriteLine("输入的格式错误"); continue;
            }
            
            ContactMemento contactMentor \= null; if (index < keyCollection.Count && caretaker.ContactMementoDic.TryGetValue(keyCollection.ElementAt(index), out contactMentor))
            {
                mobileOwner.RestoreMemento(contactMentor);
                mobileOwner.Show();
            } else {
                Console.WriteLine("输入的索引大于集合长度!");
            }
        }     
    }
}

}

复制代码

  这样就保存了多个状态,客户端可以选择恢复的状态点,具体运行结果如下所示:

   在以下情况下可以考虑使用备忘录模式:

  • 如果系统需要提供回滚操作时,使用备忘录模式非常合适。例如文本编辑器的Ctrl+Z撤销操作的实现,数据库中事务操作。

   备忘录模式具有以下优点:

  • 如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。
  • 备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。

  当然,备忘录模式也存在一定的缺点:

  • 在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。

  备忘录模式主要思想是——利用备忘录对象来对保存发起人的内部状态,当发起人需要恢复原来状态时,再从备忘录对象中进行获取,在实际开发过程也应用到这点,例如数据库中的事务处理。

一、引言

  在简单工厂模式中讲到简单工厂模式的缺点,有一点是——简单工厂模式系统难以扩展,一旦添加新产品就不得不修改简单工厂方法,这样就会造成简单工厂的实现逻辑过于复杂,然而本专题介绍的工厂方法模式可以解决简单工厂模式中存在的这个问题,下面就具体看看工厂模式是如何解决该问题的。

二、工厂方法模式的实现

  工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。下面看下工厂模式的具体实现代码(这里还是以简单工厂模式中点菜的例子来实现):

复制代码

namespace 设计模式之工厂方法模式
{ ///


/// 菜抽象类 ///

public abstract class Food
{ // 输出点了什么菜
public abstract void Print();
} ///
/// 西红柿炒鸡蛋这道菜 ///

public class TomatoScrambledEggs : Food
{ public override void Print()
{
Console.WriteLine(“西红柿炒蛋好了!”);
}
} ///
/// 土豆肉丝这道菜 ///

public class ShreddedPorkWithPotatoes : Food
{ public override void Print()
{
Console.WriteLine(“土豆肉丝好了”);
}
} ///
/// 抽象工厂类 ///

public abstract class Creator
{ // 工厂方法
public abstract Food CreateFoddFactory();
} ///
/// 西红柿炒蛋工厂类 ///

public class TomatoScrambledEggsFactory:Creator
{ ///
/// 负责创建西红柿炒蛋这道菜 ///

///
public override Food CreateFoddFactory()
{ return new TomatoScrambledEggs();
}
} ///
/// 土豆肉丝工厂类 ///

public class ShreddedPorkWithPotatoesFactory:Creator
{ ///
/// 负责创建土豆肉丝这道菜 ///

///
public override Food CreateFoddFactory()
{ return new ShreddedPorkWithPotatoes();
}
} ///
/// 客户端调用 ///

class Client
{ static void Main(string[] args)
{ // 初始化做菜的两个工厂()
Creator shreddedPorkWithPotatoesFactory = new ShreddedPorkWithPotatoesFactory();
Creator tomatoScrambledEggsFactory = new TomatoScrambledEggsFactory(); // 开始做西红柿炒蛋
Food tomatoScrambleEggs = tomatoScrambledEggsFactory.CreateFoddFactory();
tomatoScrambleEggs.Print(); //开始做土豆肉丝
Food shreddedPorkWithPotatoes = shreddedPorkWithPotatoesFactory.CreateFoddFactory();
shreddedPorkWithPotatoes.Print();

        Console.Read();
    }
}  

}

复制代码

使用工厂方法实现的系统,如果系统需要添加新产品时,我们可以利用多态性来完成系统的扩展,对于抽象工厂类和具体工厂中的代码都不需要做任何改动。例如,我们我们还想点一个“肉末茄子”,此时我们只需要定义一个肉末茄子具体工厂类肉末茄子类就可以。而不用像简单工厂模式中那样去修改工厂类中的实现(具体指添加case语句)。具体代码为:

复制代码

///


/// 肉末茄子这道菜 ///

public class MincedMeatEggplant : Food
{ ///
/// 重写抽象类中的方法 ///

public override void Print()
{
Console.WriteLine(“肉末茄子好了”);
}
} ///
/// 肉末茄子工厂类,负责创建肉末茄子这道菜 ///

public class MincedMeatEggplantFactory : Creator
{ ///
/// 负责创建肉末茄子这道菜 ///

///
public override Food CreateFoddFactory()
{ return new MincedMeatEggplant();
}
} ///
/// 客户端调用 ///

class Client
{ static void Main(string[] args)
{ // 如果客户又想点肉末茄子了 // 再另外初始化一个肉末茄子工厂
Creator minceMeatEggplantFactor = new MincedMeatEggplantFactory(); // 利用肉末茄子工厂来创建肉末茄子这道菜
Food minceMeatEggplant = minceMeatEggplantFactor.CreateFoddFactory();
minceMeatEggplant.Print();

        Console.Read();
    }
} 

复制代码

三、工厂方法模式的UML图

讲解完工厂模式的具体实现之后,让我们看下工厂模式中各类之间的UML图:

从UML图可以看出,在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应的。针对UML图的解释如下:

Creator类:充当抽象工厂角色,任何具体工厂都必须继承该抽象类

TomatoScrambledEggsFactory和ShreddedPorkWithPotatoesFactory类:充当具体工厂角色,用来创建具体产品

Food类:充当抽象产品角色,具体产品的抽象类。任何具体产品都应该继承该类

TomatoScrambledEggs和ShreddedPorkWithPotatoes类:充当具体产品角色,实现抽象产品类对定义的抽象方法,由具体工厂类创建,它们之间有一一对应的关系。

四、.NET中实现了工厂方法的类

.NET 类库中也有很多实现了工厂方法的类,例如Asp.net中,处理程序对象是具体用来处理请求,当我们请求一个*.aspx的文件时,此时会映射到System.Web.UI.PageHandlerFactory类上进行处理,而对*.ashx的请求将映射到System.Web.UI.SimpleHandlerFactory类中(这两个类都是继承于IHttpHandlerFactory接口的),关于这点说明我们可以在“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.Config”文件中找到相关定义,具体定义如下:

下面我们就具体看下工厂方法模式在Asp.net中是如何实现的,如果对一个Index.aspx页面发出请求时,将会调用PageHandlerFactory中GetHandler方法来创建一个Index.aspx对象,它们之间的类图关系如下:

五、总结

工厂方法模式通过面向对象编程中的多态性来将对象的创建延迟到具体工厂中,从而解决了简单工厂模式中存在的问题,也很好地符合了开放封闭原则(即对扩展开发,对修改封闭)。

一、引言

在上一专题中介绍了工厂方法模式,工厂方法模式是为了克服简单工厂模式的缺点而设计出来的,简单工厂模式的工厂类随着产品类的增加需要增加额外的代码),而工厂方法模式每个具体工厂类只完成单个实例的创建,所以它具有很好的可扩展性。但是在现实生活中,一个工厂只创建单个产品这样的例子很少,因为现在的工厂都多元化了,一个工厂创建一系列的产品,如果我们要设计这样的系统时,工厂方法模式显然在这里不适用,然后抽象工厂模式却可以很好地解决一系列产品创建的问题,这是本专题所要介绍的内容。

二、抽象工厂详细介绍

这里首先以一个生活中抽象工厂的例子来实现一个抽象工厂,然后再给出抽象工厂的定义和UML图来帮助大家更好地掌握抽象工厂模式,同时大家在理解的时候,可以对照抽象工厂生活中例子的实现和它的定义来加深抽象工厂的UML图理解。

2.1 抽象工厂的具体实现

下面就以生活中 “绝味” 连锁店的例子来实现一个抽象工厂模式。例如,绝味鸭脖想在江西南昌和上海开分店,但是由于当地人的口味不一样,在南昌的所有绝味的东西会做的辣一点,而上海不喜欢吃辣的,所以上海的所有绝味的东西都不会做的像南昌的那样辣,然而这点不同导致南昌绝味工厂和上海的绝味工厂生成所有绝味的产品都不同,也就是某个具体工厂需要负责一系列产品(指的是绝味所有食物)的创建工作,下面就具体看看如何使用抽象工厂模式来实现这种情况。

复制代码

1 ///


2 /// 下面以绝味鸭脖连锁店为例子演示下抽象工厂模式 3 /// 因为每个地方的喜欢的口味不一样,有些地方喜欢辣点的,有些地方喜欢吃不辣点 4 /// 客户端调用 5 ///

6 class Client 7 {
8 static void Main(string[] args)
9 {
10 // 南昌工厂制作南昌的鸭脖和鸭架
11 AbstractFactory nanChangFactory = new NanChangFactory(); 12 YaBo nanChangYabo = nanChangFactory.CreateYaBo(); 13 nanChangYabo.Print();
14 YaJia nanChangYajia= nanChangFactory.CreateYaJia(); 15 nanChangYajia.Print();
16
17 // 上海工厂制作上海的鸭脖和鸭架
18 AbstractFactory shangHaiFactory = new ShangHaiFactory(); 19 shangHaiFactory.CreateYaBo().Print();
20 shangHaiFactory.CreateYaJia().Print();
21
22 Console.Read();
23 }
24 }
25
26 ///
27 /// 抽象工厂类,提供创建两个不同地方的鸭架和鸭脖的接口 28 ///

29 public abstract class AbstractFactory 30 {
31 // 抽象工厂提供创建一系列产品的接口,这里作为例子,只给出了绝味中鸭脖和鸭架的创建接口
32 public abstract YaBo CreateYaBo(); 33 public abstract YaJia CreateYaJia(); 34 }
35
36 ///
37 /// 南昌绝味工厂负责制作南昌的鸭脖和鸭架 38 ///

39 public class NanChangFactory : AbstractFactory 40 {
41 // 制作南昌鸭脖
42 public override YaBo CreateYaBo() 43 {
44 return new NanChangYaBo(); 45 }
46 // 制作南昌鸭架
47 public override YaJia CreateYaJia() 48 {
49 return new NanChangYaJia(); 50 }
51 }
52
53 ///
54 /// 上海绝味工厂负责制作上海的鸭脖和鸭架 55 ///

56 public class ShangHaiFactory : AbstractFactory 57 {
58 // 制作上海鸭脖
59 public override YaBo CreateYaBo() 60 {
61 return new ShangHaiYaBo(); 62 }
63 // 制作上海鸭架
64 public override YaJia CreateYaJia() 65 {
66 return new ShangHaiYaJia(); 67 }
68 }
69
70 ///
71 /// 鸭脖抽象类,供每个地方的鸭脖类继承 72 ///

73 public abstract class YaBo 74 {
75 ///
76 /// 打印方法,用于输出信息 77 ///

78 public abstract void Print(); 79 }
80
81 ///
82 /// 鸭架抽象类,供每个地方的鸭架类继承 83 ///

84 public abstract class YaJia 85 {
86 ///
87 /// 打印方法,用于输出信息 88 ///

89 public abstract void Print(); 90 }
91
92 ///
93 /// 南昌的鸭脖类,因为江西人喜欢吃辣的,所以南昌的鸭脖稍微会比上海做的辣 94 ///

95 public class NanChangYaBo : YaBo 96 {
97 public override void Print() 98 {
99 Console.WriteLine(“南昌的鸭脖”); 100 } 101 } 102
103 ///
104 /// 上海的鸭脖没有南昌的鸭脖做的辣 105 ///

106 public class ShangHaiYaBo : YaBo 107 { 108 public override void Print() 109 { 110 Console.WriteLine(“上海的鸭脖”); 111 } 112 } 113
114 ///
115 /// 南昌的鸭架 116 ///

117 public class NanChangYaJia : YaJia 118 { 119 public override void Print() 120 { 121 Console.WriteLine(“南昌的鸭架子”); 122 } 123 } 124
125 ///
126 /// 上海的鸭架 127 ///

128 public class ShangHaiYaJia : YaJia 129 { 130 public override void Print() 131 { 132 Console.WriteLine(“上海的鸭架子”); 133 } 134 }

复制代码

2.2 抽象工厂模式的定义和类图

上面代码中都有详细的注释,这里就不再解释上面的代码了,下面就具体看看抽象工厂模式的定义吧(理解定义可以参考上面的实现来加深理解):

抽象工厂模式:提供一个创建产品的接口来负责创建相关或依赖的对象,而不具体明确指定具体类

抽象工厂允许客户使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么。这样客户就可以从具体产品中被解耦。下面通过抽象工模式的类图来了解各个类中之间的关系:

2.3 抽象工厂应对需求变更

看完上面抽象工厂的实现之后,如果 “绝味”公司又想在湖南开一家分店怎么办呢? 因为湖南人喜欢吃麻辣的,下面就具体看看应用了抽象工厂模式的系统是如何应对这种需求的。

复制代码

///


/// 如果绝味又想开一家湖南的分店时,因为湖南喜欢吃麻的 /// 所以这是有需要有一家湖南的工厂专门制作 ///

public class HuNanFactory : AbstractFactory
{ // 制作湖南鸭脖
public override YaBo CreateYaBo()
{ return new HuNanYaBo();
} // 制作湖南鸭架
public override YaJia CreateYaJia()
{ return new HuNanYajia();
}
} ///
/// 湖南的鸭脖 ///

public class HuNanYaBo : YaBo
{ public override void Print()
{
Console.WriteLine(“湖南的鸭脖”);
}
} ///
/// 湖南的鸭架 ///

public class HuNanYajia : YaJia
{ public override void Print()
{
Console.WriteLine(“湖南的鸭架子”);
}
}

复制代码

此时,只需要添加三个类:一个是湖南具体工厂类,负责创建湖南口味的鸭脖和鸭架,另外两个类是具有湖南口味的鸭脖类和鸭架类。从上面代码看出,抽象工厂对于系列产品的变化支持 “开放——封闭”原则(指的是要求系统对扩展开放,对修改封闭),扩展起来非常简便,但是,抽象工厂对于添加新产品这种情况就不支持”开放——封闭 “原则,这也是抽象工厂的缺点所在,这点会在第四部分详细介绍。

三、抽象工厂的分析

抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展,这真是抽象工厂模式的优点所在,然后抽象模式同时也存在不足的地方。下面就具体看下抽象工厂的缺点(缺点其实在前面的介绍中以已经涉及了):

抽象工厂模式很难支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

知道了抽象工厂的优缺点之后,也就能很好地把握什么情况下考虑使用抽象工厂模式了,下面就具体看看使用抽象工厂模式的系统应该符合那几个前提:

  • 一个系统不要求依赖产品类实例如何被创建、组合和表达的表达,这点也是所有工厂模式应用的前提。
  • 这个系统有多个系列产品,而系统中只消费其中某一系列产品
  • 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现。

四、.NET中抽象工厂模式实现

抽象工厂模式在实际中的应用也是相当频繁的,然而在我们.NET类库中也存在应用抽象工厂模式的类,这个类就是System.Data.Common.DbProviderFactory,这个类位于System.Data.dll程序集中,该类扮演抽象工厂模式中抽象工厂的角色,我们可以用reflector反编译工具查看该类的实现:

复制代码

/// 扮演抽象工厂的角色
/// 创建连接数据库时所需要的对象集合,
/// 这个对象集合包括有 DbConnection对象(这个是抽象产品类,如绝味例子中的YaBo类)、DbCommand类、DbDataAdapter类,针对不同的具体工厂都需要实现该抽象类中方法,public abstract class DbProviderFactory
{ // 提供了创建具体产品的接口方法
protected DbProviderFactory(); public virtual DbCommand CreateCommand(); public virtual DbCommandBuilder CreateCommandBuilder(); public virtual DbConnection CreateConnection(); public virtual DbConnectionStringBuilder CreateConnectionStringBuilder(); public virtual DbDataAdapter CreateDataAdapter(); public virtual DbDataSourceEnumerator CreateDataSourceEnumerator(); public virtual DbParameter CreateParameter(); public virtual CodeAccessPermission CreatePermission(PermissionState state);
}

复制代码

DbProviderFactory类是一个抽象工厂类,该类提供了创建数据库连接时所需要的对象集合的接口,实际创建的工作在其子类工厂中进行,微软使用的是SQL Server数据库,因此提供了连接SQL Server数据的具体工厂实现,具体代码可以用反编译工具查看,具体代码如下:

复制代码

/// 扮演着具体工厂的角色,用来创建连接SQL Server数据所需要的对象
public sealed class SqlClientFactory : DbProviderFactory, IServiceProvider
{ // Fields
public static readonly SqlClientFactory Instance = new SqlClientFactory(); // 构造函数
private SqlClientFactory()
{
} // 重写抽象工厂中的方法
public override DbCommand CreateCommand()
{ // 创建具体产品
return new SqlCommand();
} public override DbCommandBuilder CreateCommandBuilder()
{ return new SqlCommandBuilder();
} public override DbConnection CreateConnection()
{ return new SqlConnection();
} public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{ return new SqlConnectionStringBuilder();
} public override DbDataAdapter CreateDataAdapter()
{ return new SqlDataAdapter();
} public override DbDataSourceEnumerator CreateDataSourceEnumerator()
{ return SqlDataSourceEnumerator.Instance;
} public override DbParameter CreateParameter()
{ return new SqlParameter();
} public override CodeAccessPermission CreatePermission(PermissionState state)
{ return new SqlClientPermission(state);
}
}

复制代码

因为微软只给出了连接SQL Server的具体工厂的实现,我们也可以自定义连接Oracle、MySql的具体工厂的实现。

五、总结

到这里,抽象工厂模式的介绍就结束,在下一专题就将为大家介绍建造模式。

程序源码:抽象工厂模式实现

C#自动识别文件编码 - 晓晨Master - 博客园

Excerpt

在做导入微信商户后台退款数据时,无论怎么设置编码导出来都是乱码,后来在网上找了这个识别文件编码的代码,感觉不错。 最后识别出来是gb2312,看来我还是太渣了,只能吃土了,竟然忘记了这个编码。 下面,上代码。


在做导入微信商户后台退款数据时,无论怎么设置编码导出来都是乱码,后来在网上找了这个识别文件编码的代码,感觉不错。

最后识别出来是gb2312,看来我还是太渣了,只能吃土了,竟然忘记了这个编码。

下面,上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<span>  1</span> <span>///</span> <span>&lt;summary&gt;</span>   
<span> 2</span> <span>///</span><span> 用于取得一个文本文件的编码方式(Encoding)。
</span><span> 3</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span> 4</span> <span>public</span> <span>class</span><span> TxtFileEncoder
</span><span> 5</span> <span> {
</span><span> 6</span> <span>public</span><span> TxtFileEncoder()
</span><span> 7</span> <span> {
</span><span> 8</span> <span>//</span>
<span> 9</span> <span>//</span><span> TODO: 在此处添加构造函数逻辑
</span><span> 10</span> <span>//</span>
<span> 11</span> <span> }
</span><span> 12</span> <span>///</span> <span>&lt;summary&gt;</span>
<span> 13</span> <span>///</span><span> 取得一个文本文件的编码方式。如果无法在文件头部找到有效的前导符,Encoding.Default将被返回。
</span><span> 14</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span> 15</span> <span>///</span> <span>&lt;param name="fileName"&gt;</span><span>文件名。</span><span>&lt;/param&gt;</span>
<span> 16</span> <span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span> 17</span> <span>public</span> <span>static</span> Encoding GetEncoding(<span>string</span><span> fileName)
</span><span> 18</span> <span> {
</span><span> 19</span> <span>return</span><span> GetEncoding(fileName, Encoding.Default);
</span><span> 20</span> <span> }
</span><span> 21</span> <span>///</span> <span>&lt;summary&gt;</span>
<span> 22</span> <span>///</span><span> 取得一个文本文件流的编码方式。
</span><span> 23</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span> 24</span> <span>///</span> <span>&lt;param name="stream"&gt;</span><span>文本文件流。</span><span>&lt;/param&gt;</span>
<span> 25</span> <span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span> 26</span> <span>public</span> <span>static</span><span> Encoding GetEncoding(FileStream stream)
</span><span> 27</span> <span> {
</span><span> 28</span> <span>return</span><span> GetEncoding(stream, Encoding.Default);
</span><span> 29</span> <span> }
</span><span> 30</span> <span>///</span> <span>&lt;summary&gt;</span>
<span> 31</span> <span>///</span><span> 取得一个文本文件的编码方式。
</span><span> 32</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span> 33</span> <span>///</span> <span>&lt;param name="fileName"&gt;</span><span>文件名。</span><span>&lt;/param&gt;</span>
<span> 34</span> <span>///</span> <span>&lt;param name="defaultEncoding"&gt;</span><span>默认编码方式。当该方法无法从文件的头部取得有效的前导符时,将返回该编码方式。</span><span>&lt;/param&gt;</span>
<span> 35</span> <span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span> 36</span> <span>public</span> <span>static</span> Encoding GetEncoding(<span>string</span><span> fileName, Encoding defaultEncoding)
</span><span> 37</span> <span> {
</span><span> 38</span> FileStream fs = <span>new</span><span> FileStream(fileName, FileMode.Open);
</span><span> 39</span> Encoding targetEncoding =<span> GetEncoding(fs, defaultEncoding);
</span><span> 40</span> <span> fs.Close();
</span><span> 41</span> <span>return</span><span> targetEncoding;
</span><span> 42</span> <span> }
</span><span> 43</span> <span>///</span> <span>&lt;summary&gt;</span>
<span> 44</span> <span>///</span><span> 取得一个文本文件流的编码方式。
</span><span> 45</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span> 46</span> <span>///</span> <span>&lt;param name="stream"&gt;</span><span>文本文件流。</span><span>&lt;/param&gt;</span>
<span> 47</span> <span>///</span> <span>&lt;param name="defaultEncoding"&gt;</span><span>默认编码方式。当该方法无法从文件的头部取得有效的前导符时,将返回该编码方式。</span><span>&lt;/param&gt;</span>
<span> 48</span> <span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span> 49</span> <span>public</span> <span>static</span><span> Encoding GetEncoding(FileStream stream, Encoding defaultEncoding)
</span><span> 50</span> <span> {
</span><span> 51</span> Encoding targetEncoding =<span> defaultEncoding;
</span><span> 52</span> <span>if</span> (stream != <span>null</span> &amp;&amp; stream.Length &gt;= <span>2</span><span>)
</span><span> 53</span> <span> {
</span><span> 54</span> <span>//</span><span>保存文件流的前4个字节 </span>
<span> 55</span> <span>byte</span> byte1 = <span>0</span><span>;
</span><span> 56</span> <span>byte</span> byte2 = <span>0</span><span>;
</span><span> 57</span> <span>byte</span> byte3 = <span>0</span><span>;
</span><span> 58</span> <span>byte</span> byte4 = <span>0</span><span>;
</span><span> 59</span> <span>//</span><span>保存当前Seek位置 </span>
<span> 60</span> <span>long</span> origPos = stream.Seek(<span>0</span><span>, SeekOrigin.Begin);
</span><span> 61</span> stream.Seek(<span>0</span><span>, SeekOrigin.Begin);
</span><span> 62</span>
<span> 63</span> <span>int</span> nByte =<span> stream.ReadByte();
</span><span> 64</span> byte1 =<span> Convert.ToByte(nByte);
</span><span> 65</span> byte2 =<span> Convert.ToByte(stream.ReadByte());
</span><span> 66</span> <span>if</span> (stream.Length &gt;= <span>3</span><span>)
</span><span> 67</span> <span> {
</span><span> 68</span> byte3 =<span> Convert.ToByte(stream.ReadByte());
</span><span> 69</span> <span> }
</span><span> 70</span> <span>if</span> (stream.Length &gt;= <span>4</span><span>)
</span><span> 71</span> <span> {
</span><span> 72</span> byte4 =<span> Convert.ToByte(stream.ReadByte());
</span><span> 73</span> <span> }
</span><span> 74</span> <span>//</span><span>根据文件流的前4个字节判断Encoding
</span><span> 75</span> <span>//</span><span>Unicode {0xFF, 0xFE};
</span><span> 76</span> <span>//</span><span>BE-Unicode {0xFE, 0xFF};
</span><span> 77</span> <span>//</span><span>UTF8 = {0xEF, 0xBB, 0xBF}; </span>
<span> 78</span> <span>if</span> (byte1 == <span>0xFE</span> &amp;&amp; byte2 == <span>0xFF</span>)<span>//</span><span>UnicodeBe </span>
<span> 79</span> <span> {
</span><span> 80</span> targetEncoding =<span> Encoding.BigEndianUnicode;
</span><span> 81</span> <span> }
</span><span> 82</span> <span>if</span> (byte1 == <span>0xFF</span> &amp;&amp; byte2 == <span>0xFE</span> &amp;&amp; byte3 != <span>0xFF</span>)<span>//</span><span>Unicode </span>
<span> 83</span> <span> {
</span><span> 84</span> targetEncoding =<span> Encoding.Unicode;
</span><span> 85</span> <span> }
</span><span> 86</span> <span>if</span> (byte1 == <span>0xEF</span> &amp;&amp; byte2 == <span>0xBB</span> &amp;&amp; byte3 == <span>0xBF</span>)<span>//</span><span>UTF8 </span>
<span> 87</span> <span> {
</span><span> 88</span> targetEncoding =<span> Encoding.UTF8;
</span><span> 89</span> <span> }
</span><span> 90</span> <span>//</span><span>恢复Seek位置 </span>
<span> 91</span> <span> stream.Seek(origPos, SeekOrigin.Begin);
</span><span> 92</span> <span> }
</span><span> 93</span> <span>return</span><span> targetEncoding;
</span><span> 94</span> <span> }
</span><span> 95</span>
<span> 96</span>
<span> 97</span>
<span> 98</span> <span>//</span><span> 新增加一个方法,解决了不带BOM的 UTF8 编码问题 </span>
<span> 99</span>
<span>100</span> <span>///</span> <span>&lt;summary&gt;</span>
<span>101</span> <span>///</span><span> 通过给定的文件流,判断文件的编码类型
</span><span>102</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span>103</span> <span>///</span> <span>&lt;param name="fs"&gt;</span><span>文件流</span><span>&lt;/param&gt;</span>
<span>104</span> <span>///</span> <span>&lt;returns&gt;</span><span>文件的编码类型</span><span>&lt;/returns&gt;</span>
<span>105</span> <span>public</span> <span>static</span><span> System.Text.Encoding GetEncoding(Stream fs)
</span><span>106</span> <span> {
</span><span>107</span> <span>byte</span>[] Unicode = <span>new</span> <span>byte</span>[] { <span>0xFF</span>, <span>0xFE</span>, <span>0x41</span><span> };
</span><span>108</span> <span>byte</span>[] UnicodeBIG = <span>new</span> <span>byte</span>[] { <span>0xFE</span>, <span>0xFF</span>, <span>0x00</span><span> };
</span><span>109</span> <span>byte</span>[] UTF8 = <span>new</span> <span>byte</span>[] { <span>0xEF</span>, <span>0xBB</span>, <span>0xBF</span> }; <span>//</span><span>带BOM </span>
<span>110</span> Encoding reVal =<span> Encoding.Default;
</span><span>111</span>
<span>112</span> BinaryReader r = <span>new</span><span> BinaryReader(fs, System.Text.Encoding.Default);
</span><span>113</span> <span>byte</span>[] ss = r.ReadBytes(<span>4</span><span>);
</span><span>114</span> <span>if</span> (ss[<span>0</span>] == <span>0xFE</span> &amp;&amp; ss[<span>1</span>] == <span>0xFF</span> &amp;&amp; ss[<span>2</span>] == <span>0x00</span><span>)
</span><span>115</span> <span> {
</span><span>116</span> reVal =<span> Encoding.BigEndianUnicode;
</span><span>117</span> <span> }
</span><span>118</span> <span>else</span> <span>if</span> (ss[<span>0</span>] == <span>0xFF</span> &amp;&amp; ss[<span>1</span>] == <span>0xFE</span> &amp;&amp; ss[<span>2</span>] == <span>0x41</span><span>)
</span><span>119</span> <span> {
</span><span>120</span> reVal =<span> Encoding.Unicode;
</span><span>121</span> <span> }
</span><span>122</span> <span>else</span>
<span>123</span> <span> {
</span><span>124</span> <span>if</span> (ss[<span>0</span>] == <span>0xEF</span> &amp;&amp; ss[<span>1</span>] == <span>0xBB</span> &amp;&amp; ss[<span>2</span>] == <span>0xBF</span><span>)
</span><span>125</span> <span> {
</span><span>126</span> reVal =<span> Encoding.UTF8;
</span><span>127</span> <span> }
</span><span>128</span> <span>else</span>
<span>129</span> <span> {
</span><span>130</span> <span>int</span><span> i;
</span><span>131</span> <span>int</span>.TryParse(fs.Length.ToString(), <span>out</span><span> i);
</span><span>132</span> ss =<span> r.ReadBytes(i);
</span><span>133</span>
<span>134</span> <span>if</span><span> (IsUTF8Bytes(ss))
</span><span>135</span> reVal =<span> Encoding.UTF8;
</span><span>136</span> <span> }
</span><span>137</span> <span> }
</span><span>138</span> <span> r.Close();
</span><span>139</span> <span>return</span><span> reVal;
</span><span>140</span>
<span>141</span> <span> }
</span><span>142</span>
<span>143</span> <span>///</span> <span>&lt;summary&gt;</span>
<span>144</span> <span>///</span><span> 判断是否是不带 BOM 的 UTF8 格式
</span><span>145</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span>146</span> <span>///</span> <span>&lt;param name="data"&gt;&lt;/param&gt;</span>
<span>147</span> <span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span>148</span> <span>private</span> <span>static</span> <span>bool</span> IsUTF8Bytes(<span>byte</span><span>[] data)
</span><span>149</span> <span> {
</span><span>150</span> <span>int</span> charByteCounter = <span>1</span>;  <span>//</span><span>计算当前正分析的字符应还有的字节数 </span>
<span>151</span> <span>byte</span> curByte; <span>//</span><span>当前分析的字节. </span>
<span>152</span> <span>for</span> (<span>int</span> i = <span>0</span>; i &lt; data.Length; i++<span>)
</span><span>153</span> <span> {
</span><span>154</span> curByte =<span> data[i];
</span><span>155</span> <span>if</span> (charByteCounter == <span>1</span><span>)
</span><span>156</span> <span> {
</span><span>157</span> <span>if</span> (curByte &gt;= <span>0x80</span><span>)
</span><span>158</span> <span> {
</span><span>159</span> <span>//</span><span>判断当前 </span>
<span>160</span> <span>while</span> (((curByte &lt;&lt;= <span>1</span>) &amp; <span>0x80</span>) != <span>0</span><span>)
</span><span>161</span> <span> {
</span><span>162</span> charByteCounter++<span>;
</span><span>163</span> <span> }
</span><span>164</span> <span>//</span><span>标记位首位若为非0 则至少以2个1开始 如:110XXXXX...........1111110X  </span>
<span>165</span> <span>if</span> (charByteCounter == <span>1</span> || charByteCounter &gt; <span>6</span><span>)
</span><span>166</span> <span> {
</span><span>167</span> <span>return</span> <span>false</span><span>;
</span><span>168</span> <span> }
</span><span>169</span> <span> }
</span><span>170</span> <span> }
</span><span>171</span> <span>else</span>
<span>172</span> <span> {
</span><span>173</span> <span>//</span><span>若是UTF-8 此时第一位必须为1 </span>
<span>174</span> <span>if</span> ((curByte &amp; <span>0xC0</span>) != <span>0x80</span><span>)
</span><span>175</span> <span> {
</span><span>176</span> <span>return</span> <span>false</span><span>;
</span><span>177</span> <span> }
</span><span>178</span> charByteCounter--<span>;
</span><span>179</span> <span> }
</span><span>180</span> <span> }
</span><span>181</span> <span>if</span> (charByteCounter &gt; <span>1</span><span>)
</span><span>182</span> <span> {
</span><span>183</span> <span>throw</span> <span>new</span> Exception(<span>"</span><span>非预期的byte格式!</span><span>"</span><span>);
</span><span>184</span> <span> }
</span><span>185</span> <span>return</span> <span>true</span><span>;
</span><span>186</span> <span> }
</span><span>187</span> }

C#获取根目录的方法集合 - 晓晨Master - 博客园

Excerpt

1、取得控制台应用程序的根目录方法 方法1、Environment.CurrentDirectory 取得或设置当前工作目录的完整限定路径 方法2、AppDomain.CurrentDomain.BaseDirectory 获取基目录,它由程序集冲突解决程序用来探测程序集 2、取得Web应


1、取得控制台应用程序的根目录方法
     方法1、Environment.CurrentDirectory 取得或设置当前工作目录的完整限定路径
     方法2、AppDomain.CurrentDomain.BaseDirectory 获取基目录,它由程序集冲突解决程序用来探测程序集
 2、取得Web应用程序的根目录方法
     方法1、HttpRuntime.AppDomainAppPath.ToString();//获取承载在当前应用程序域中的应用程序的应用程序目录的物理驱动器路径。用于App_Data中获取
     方法2、Server.MapPath(“”) 或者Server.MapPath(“~/“);//返回与Web服务器上的指定的虚拟路径相对的物理文件路径
     方法3、Request.ApplicationPath;//获取服务器上ASP.NET应用程序的虚拟应用程序根目录
 3、取得WinForm应用程序的根目录方法
     1、Environment.CurrentDirectory.ToString();//获取或设置当前工作目录的完全限定路径
     2、Application.StartupPath.ToString();//获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称
     3、Directory.GetCurrentDirectory();//获取应用程序的当前工作目录
     4、AppDomain.CurrentDomain.BaseDirectory;//获取基目录,它由程序集冲突解决程序用来探测程序集
     5、AppDomain.CurrentDomain.SetupInformation.ApplicationBase;//获取或设置包含该应用程序的目录的名称
其中:以下两个方法可以获取执行文件名称
     1、Process.GetCurrentProcess().MainModule.FileName;//可获得当前执行的exe的文件名。
     2、Application.ExecutablePath;//获取启动了应用程序的可执行文件的路径,包括可执行文件的名称

获取.net的根目录的方法
方法1:System.Web.HttpContext.Current.Request.PhysicalApplicationPath
方法2:System.Web.HttpContext.Current.Server.MapPath(“./“)

总注:Server.MapPath获得的路径都是服务器上的物理路径,也就是常说的绝对路径
1、Server.MapPath(“/“)
注:获得应用程序根目录所在的位置,如 C:\Inetpub\wwwroot\。
2、Server.MapPath(“./“)
注:获得所在页面的当前目录,等价于Server.MapPath(“”)。
3、Server.MapPath(“../“)
注:获得所在页面的上级目录。
4、Server.MapPath(“~/“)
注:获得当前应用级程序的目录,如果是根目录,就是根目录,如果是虚拟目录,就是虚拟目录所在的位置,如C:\Inetpub\wwwroot\Example\。

来至:http://wenqingluomo.blog.163.com/blog/static/791717402010101931946397/

一、引言

最近在设计模式的一些内容,主要的参考书籍是《Head First 设计模式》,同时在学习过程中也查看了很多博客园中关于设计模式的一些文章的,在这里记录下我的一些学习笔记,一是为了帮助我更深入地理解设计模式,二同时可以给一些初学设计模式的朋友一些参考。首先我介绍的是设计模式中比较简单的一个模式——单例模式(因为这里只牵涉到一个类)

二、单例模式的介绍

说到单例模式,大家第一反应应该就是——什么是单例模式?,从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法罢了(设计模式其实就是帮助我们解决实际开发过程中的方法, 该方法是为了降低对象之间的耦合度,然而解决方法有很多种,所以前人就总结了一些常用的解决方法为书籍,从而把这本书就称为设计模式),下面给出单例模式的一个官方定义:确保一个类只有一个实例,并提供一个全局访问点。为了帮助大家更好地理解单例模式,大家可以结合下面的类图来进行理解,以及后面也会剖析单例模式的实现思路:

三、为什么会有单例模式

看完单例模式的介绍,自然大家都会有这样一个疑问——为什么要有单例模式的?它在什么情况下使用的?从单例模式的定义中我们可以看出——单例模式的使用自然是当我们的系统中某个对象只需要一个实例的情况,例如:操作系统中只能有一个任务管理器,操作文件时,同一时间内只允许一个实例对其操作等,既然现实生活中有这样的应用场景,自然在软件设计领域必须有这样的解决方案了(因为软件设计也是现实生活中的抽象),所以也就有了单例模式了。

四、剖析单例模式的实现思路

了解完了一些关于单例模式的基本概念之后,下面就为大家剖析单例模式的实现思路的,因为在我自己学习单例模式的时候,咋一看单例模式的实现代码确实很简单,也很容易看懂,但是我还是觉得它很陌生(这个可能是看的少的,或者自己在写代码中也用的少的缘故),而且心里总会这样一个疑问——为什么前人会这样去实现单例模式的呢?他们是如何思考的呢?后面经过自己的琢磨也就慢慢理清楚单例模式的实现思路了,并且此时也不再觉得单例模式陌生了,下面就分享我的一个剖析过程的:

我们从单例模式的概念(确保一个类只有一个实例,并提供一个访问它的全局访问点)入手,可以把概念进行拆分为两部分:(1)确保一个类只有一个实例;(2)提供一个访问它的全局访问点;下面通过采用两人对话的方式来帮助大家更快掌握分析思路:

菜鸟:怎样确保一个类只有一个实例了?

老鸟:那就让我帮你分析下,你创建类的实例会想到用什么方式来创建的呢?

新手:用new关键字啊,只要new下就创建了该类的一个实例了,之后就可以使用该类的一些属性和实例方法了

老鸟:那你想过为什么可以使用new关键字来创建类的实例吗?

菜鸟:这个还有条件的吗?………, 哦,我想起来了,如果类定义私有的构造函数就不能在外界通过new创建实例了(注:有些初学者就会问,有时候我并没有在类中定义构造函数为什么也可以使用new来创建对象,那是因为编译器在背后做了手脚了,当编译器看到我们类中没有定义构造函数,此时编译器会帮我们生成一个公有的无参构造函数)

老鸟:不错,回答的很对,这样你的疑惑就得到解答了啊

菜鸟:那我要在哪里创建类的实例了?

老鸟:你傻啊,当然是在类里面创建了(注:这样定义私有构造函数就是上面的一个思考过程的,要创建实例,自然就要有一个变量来保存该实例把,所以就有了私有变量的声明,但是实现中是定义静态私有变量,朋友们有没有想过——这里为什么定义为静态的呢?对于这个疑问的解释为:每个线程都有自己的线程栈,定义为静态主要是为了在多线程确保类有一个实例

菜鸟:哦,现在完全明白了,但是我还有另一个疑问——现在类实例创建在类内部,那外界如何获得该的一个实例来使用它了?

老鸟:这个,你可以定义一个公有方法或者属性来把该类的实例公开出去了(注:这样就有了公有方法的定义了,该方法就是提供方法问类的全局访问点)

通过上面的分析,相信大家也就很容易写出单例模式的实现代码了,下面就看看具体的实现代码(看完之后你会惊讶道:真是这样的!):

复制代码

///


/// 单例模式的实现 ///

public class Singleton
{ // 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance; // 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
} ///
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 ///

///
public static Singleton GetInstance()
{ // 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
} return uniqueInstance;
}
}

复制代码

 上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行GetInstance方法时,此时两个线程判断(uniqueInstance ==null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只运行一个线程运行就好了,也就是我们线程同步的问题了(对于线程同步大家也可以参考我线程同步的文章),具体的解决多线程的代码如下:

复制代码

///


/// 单例模式的实现 ///

public class Singleton
{ // 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance; // 定义一个标识确保线程同步
private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
} ///
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 ///

///
public static Singleton GetInstance()
{ // 当第一个线程运行到这里时,此时会对locker对象 “加锁”, // 当第二个线程运行该方法时,首先检测到locker对象为”加锁”状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象”解锁”
lock (locker)
{ // 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
} return uniqueInstance;
}
}

复制代码

上面这种解决方案确实可以解决多线程的问题,但是上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance==null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句(uniqueInstance==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”,下面具体看看实现代码的:

复制代码

///


/// 单例模式的实现 ///

public class Singleton
{ // 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance; // 定义一个标识确保线程同步
private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
} ///
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 ///

///
public static Singleton GetInstance()
{ // 当第一个线程运行到这里时,此时会对locker对象 “加锁”, // 当第二个线程运行该方法时,首先检测到locker对象为”加锁”状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象”解锁” // 双重锁定只需要一句判断就可以了
if (uniqueInstance == null)
{ lock (locker)
{ // 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
} return uniqueInstance;
}
}

复制代码

五、C#中实现了单例模式的类

 理解完了单例模式之后,菜鸟又接着问了:.NET FrameWork类库中有没有单例模式的实现呢?

经过查看,.NET类库中确实存在单例模式的实现类,不过该类不是公开的,下面就具体看看该类的一个实现的(该类具体存在于System.dll程序集,命名空间为System,大家可以用反射工具Reflector去查看源码的):

复制代码

// 该类不是一个公开类 // 但是该类的实现应用了单例模式
internal sealed class SR
{ private static SR loader; internal SR()
{
} // 主要是因为该类不是公有,所以这个全部访问点也定义为私有的了 // 但是思想还是用到了单例模式的思想的
private static SR GetLoader()
{ if (loader == null)
{
SR sr = new SR();
Interlocked.CompareExchange(ref loader, sr, null);
} return loader;
} // 这个公有方法中调用了GetLoader方法的
public static object GetObject(string name)
{
SR loader = GetLoader(); if (loader == null)
{ return null;
} return loader.resources.GetObject(name, Culture);
}
}

复制代码

六、总结

到这里,设计模式的单例模式就介绍完了,希望通过本文章大家可以对单例模式有一个更深的理解,并且希望之前没接触过单例模式或觉得单例模式陌生的朋友看完之后会惊叹:原来如此!