一、定义
简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
该模式中包含的角色及其职责
- 工厂(Creator)角色
简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
- 抽象产品(Product)角色
简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
- 具体产品(Concrete Product)角色
是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
二、图分析
1、运算类与简单工厂为依赖关系,即简单工厂类使用到运算类。简单工厂类并不知道具体实例化哪一个类,所以通过返回运算类用到哪个类就实例化哪个类。
2、其他算法类继承于运算类,具有相同的方法
三、代码实现
namespace 简单工厂模式 { class Program { static void Main(string[] args) { Operation oper; //声明对象 oper=OperationFactory.createOperate("+"); //决定实例化哪一个运算类 oper.NumberA=1; oper.NumberB=2; double result = oper.GetResult(); //获得结果 Console.WriteLine("结果是:" + result); Console.ReadLine(); } } public class Operation //运算类 { private double _numberA = 0; private double _numberB = 0; public double NumberA { get{ return _numberA;} set{ _numberA = value;} } public double NumberB { get{ return _numberB;} set{_numberB = value;} } public virtual double GetResult() //获得结果的方法 { double result=0; return result; } } class OperationAdd:Operation //继承运算累 { public override double GetResult() { double result=0; result = NumberA + NumberB; return result; } } class OperationSub : Operation //继承运算类 { public override double GetResult() { double result=0; result = NumberA - NumberB; return result; } } class OperationMul : Operation //继承运算类 { public override double GetResult() { double result=0; result = NumberA * NumberB; return result; } } class OperationDiv : Operation //继承运算类 { public override double GetResult() { double result = 0; if(NumberB==0) throw new Exception("除数不能为0."); result = NumberA / NumberB; return result; } } public class OperationFactory //简单工厂 { public static Operation createOperate(string operate) { Operation oper=null; switch(operate) { case"+": oper=new OperationAdd(); break; case"-": oper=new OperationSub(); break; case"*": oper=new OperationMul(); break; case"/": oper=new OperationDiv(); break; } return oper; } } }
四、适用情况(联系生活和以前学习经验)
- 工厂类负责创建的对象比较少;
- 客户只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心;
- 由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。
- 简单的组合查询应该会遇到(自己的理解,如果不正确请谅解)
五、知识点归纳
1、静态变量的使用
在C#程序中,没有全局变量的概念,这意味着所有的成员变量只有该类的实例才能操作这些数据,这起到了“信息隐藏”的作用。但有些时候,这样做却不是个明智的选择。
假设我们要定义一个图书类,要求该类能保存图书的数量,即每增加一本图书(定义一个实例),图书的数量应该加1。如果没有静态变量,我们需要将图书的数量保存在每本图书(实例)里,然而,这样的变量要在不同图书(实例)里重复存储,图书(实例)数量少的话,我们还能接受,如果图书(实例)数量较多的话,比如成千上万,我们无法想象这要造成多少资源(如内存、磁盘空间)的浪费,更让人无法忍受的是:因为图书(实例)的数量要保存在每本图书(实例)里,该值肯定是不同的。要使这些图书(实例)中保存的图书(实例)数量一致,我们必须在每增加一本新书(生成一个新实例)时,修改其他所有图书(实例)中保存的该值。Oh,My God!你会重新向往面向过程的程序设计方法,向往拥有全局变量的时代。但,这种局面不会出现,因为C#中为你准备好了另外一种变量类型:静态变量。它在类中类似全局变量,保存类的公共信息,所有该类的实例(对象)共享该值。
静态变量的声明方式如下:
[访问修饰符] static 数据类型 变量名;
这里的访问修饰符跟类的其它成员一样,可以是public,protected,private或internal等。
静态变量又如何使用呢?
静态变量必须使用类名来引用,而不能使用类的实例,因为,静态变量不属于任何实例,而是共有的。我们可以打个比方:在一个班级中,有些物品是个人的,我们想使用时,必须指出物品的所有者,比如说“王三的自行车”,在C#程序中我们可以使用:王三.自行车的格式。有些物品是大家共有的物品,不能以个人名义使用,而是用班级的名义使用,比如班集体出资买的篮球,只能说:“班级的篮球”,而不能说:“王三的篮球”。这绝对是不行的,这对其他人绝对是不公平的,我们可以联想到许多贪官污吏就是使用了不属于自己的东西,或以个人名义使用公家的东西而葬送了自己。
说一句有用的就是:静态变量是用类名来引用它。即:类名.静态变量名;
另外,对于静态变量在声明时,如果没有给出初始值或使用前没有赋任何值的话,系统会给他们一个默认值:对于整型数据默认值为0;单精度数据为:0.0f;双精度数据为0.0;布尔型数据为False;引用型数据为null。
2.静态方法
静态方法与静态变量一样,不属于任何特定的实例,属于类全体成员共有,由类名来调用。但要注意以下几点:
静态方法只能访问类的静态成员,不能访问类的非静态成员;
非静态方法可以访问类的静态成员,也可以访问类的非静态成员;
静态方法不能使用实例来调用,只能使用类名来调用。这里用一个具体的例子来说明:
2、0d的思考
对于静态变量在声明时,如果没有给出初始值或使用前没有赋任何值的话,系统会给他们一个默认值:对于整型数据默认值为0;单精度数据为:0.0f;双精度数据为0.0;布尔型数据为False;引用型数据为null。
0d和0.0d的区别:需要的精度不同
3、封装的两种形式
两种封装方法(引用信秀)
传统的读、写方法封装:
//有一个类Name,为了操纵这个类中的数据(string name)我们定义了一个读方法和一个写方法。 public class Name { private string name; // 读方法 public string GetName() { return name; } //写方法 public void SetName(string n) { name = n; } public static void Main(string[] args) { Name Country = new Name(); Country.SetName("China"); Console.WriteLine("The Name is :" + Country.GetName()); } } 上面的方法保证了私有成员name不被外部成员所破坏,我们实例化一个对象Country来实现读和写数据。在上面的例子中,我们不能直接访问类Name 的实例Country中的私有数据(string name),我们只能通过SetName和GetName两个方法来读和写数据。 用属性来实现封装: 属性是类的基本组成部分,通过对属性的读和写来保护类中的域。使用属性来实现封装比上一种方法更简单。 public class Name { private string name; //需要封装的属性 public string CountryName { get{return name;} set{name=value;} } } public class Name1 { public static void Main(string[] args) { Name Country= new Name(); Country.CountryName = "China"; Console.WriteLine("The Country is :{0}",Country.CountryName); } }
上面的例子通过属性实现了stringname的封装,其中属性具有两种操作get和set。Get用来返回属性域的值;Set通过value这个变量来给属性域赋值。
4、多态实现的四种形式
接口多态性。
- 继承多态性。
- 通过抽象类实现的多态性。
- 虚方法
5、null的思考
- 等价于没有任何值、是未知数(Empty代表瓶子是空的,null代表瓶子都没有)。
- NULL与0、空字符串、空格都不同,NULL没有分配存储空间。
- 对空值做加、减、乘、除等运算操作,结果仍为空(此设计模式设计的)。
- NULL的处理使用NVL函数。
- 比较时使用关键字用“is null”和“is not null”。
- 空值不能被索引,所以查询时有些符合条件的数据可能查不出来,count(*)中,用nvl(列名,0)处理后再查。
- 排序时比其他数据都大(索引默认是降序排列,小→大),所以NULL值总是排在最后。
- Null代表未初始化的引用类型成员
引用类型成员并不一定会在定义或构造函数中就初始化。一种情况,是为了性能考虑,进行延时初始化,如单例模式中。但是,null值不应该传递给外部。
另一种情况,是考虑扩展性,将个别成员留空。如适配器模式中,Asp.Net Page在呈现时,会检查其Adapter属性是否为null,如不是null,则调用Adapter负责呈现。
- 尽可能减少返回null的场景
留意了下.Net Framework的函数,返回null的还真是很少见。一返回null,调用函数就不得不进行判断,增加了复杂性,及出错的机率。通过对代码结构进行适当调整优化,应该可以减少null的判断。尤其是框架开发,在公开的API函数中,返回Null的情况应该越少越好。例外的可能是一些按索引取值的属性,如HttpRequest.Form。
如果是要返回一个集合类型,只要执行无异常,宁可为空也不要返回null。
- 要对外部数据进行null判断
在你公开的API中,必须考虑被其他程序集调用,接收到null参数的情况,不管是什么原因传入的。
不要让null参数表示特殊含义
- 让程序抛出ArgumentNullException而不是NullReferenceException
六、出错点
1、客户端调试时没有输出结果
2、UML图中返回类型不显示:通过return和显示operation