编辑推荐: |
本文主要介绍设计模式之UML类图的常见关系,希望对您有所帮助。
本文来源于网络,由火龙果软件Linda编辑,推荐。 |
|
设计模式之UML类图的常见关系(一)
本篇会讲解在UML类图中,常见几种关系: 泛化(Generalization),依赖(Dependency),关联(Association),聚合(Aggregation),组合(Composition)。
1.泛化关系
泛化关系是继承或实现的关系,是is a关系,具体表现为类与类的继承,接口与接口的继承,类对接口的实现关系。
2.依赖关系
依赖关系表示为一个类使用另一个类,这种使用关系是具有偶然性的、临时性的、非常弱的,一个类的变化会影响到另一个类,是use a关系,如果类A依赖于类B,那么类B可以是类A的局部变量,或类A方法的参数,或静态方法的调用。
3.关联关系
关联关系是一种强依赖关系,这种关系不存在依赖关系的偶然性,关系也不是临时的,是长期的,稳定的。双方的关系是平等的,可以单向关联也可以是双向关联。假如类A关联了类B,则类B是类A的全局变量(注意是全局变量,再看看上面的依赖关系),大多数关联都是单向关联,这比较容易维护,关于关联,在生活中我们常会说,类A持有类B的引用。
4.聚合关系
聚合关系是特殊的关联关系,是一种强的关联关系,他体现的是整体与部分关系,即has-a的关系,但是整体和部分是可以分离的,注意,是可以分离的。普通关联关系的两个类处于同一层次上,是平级的,而聚合关系的两个类处于不同的层次,一个是整体,一个是部分。同时,是一种弱的“拥有”关系。体现的是A对象可以包含B对象,但B对象不是A对象的组成部分。具体表现为,如果A由B聚合成,表现为A包含有B的全局对象,但是B对象可以不在A创建的时刻创建,这句话非常有意义,它在代码中通常体现成依赖注入的setter方法,即A对象可以随时创建B对象,再想想这不就体现了整体和部分是可以分离了吗?创建整体的时候可以不创建部分。
5.组合关系
组合关系也是特殊的关联关系,它体现一种contains a(拥有)关系,这种关系是比聚合还要强,也称为强聚合。体现了严格的整体和部分关系,两者是不可分割的,它们的生命周期是一致的。如果A由B组成,那么A就包含B的全局变量,并在创建A的同时创建B,在代码上我们通常是使用构造函数进行实现,也是依赖注入中构造函数的实现。
最后,我们来总结一下,泛化就不用多少了,大家都懂的,就是继承和实现接口,重点说下其它的吧,依赖,ClassB体现为ClassA的局部变量,我想用就用,用了就有关系,不用就没关系;关联,ClassB体现为ClassA的全局变量,不管你用不用,反正你知道我的存在了,持有了我的引用。聚合,是特殊的关联关系,用了就加强了关系,不用还是我只知道你的存在。聚合可以方便的持有多个类的引用,如使用List<>,所以当你发现有List<>等集合是可以使用聚合来表示,比如观察者模式的结构。组合,体现最强的关系,比如人出身了,必定也有头部吧,不然我真无法想象这个世界了。
说下最基本的依赖注入方式吧,一种是setter方法注入,如聚合关系中提现的,使用一个方法来完成注入,另一种是构造函数注入,如组合关系中提现的,还有一种是接口注入,一般不常用也就不说了。以后会详细在番外篇中讲解。
设计模式之简单工厂(二)
学习GOF23种设计模式,从学习简单工厂开始是个不错的选择,简单工厂虽不属于GOF设计模式中的一种,但它在开发中是非常常用的,也充分体现了面向对象开发的要点和好处,所以理解简单工厂,对于后面学习其它的模式是非常有帮助的。
一、引出模式
我们时常听说面向对象开发很厉害,所以在开发中我们会时不时尝试用面向对象的方法去开发程序,然后,就会遇到这种情况:面对对象开发,我们要有接口,我们要有实现接口的具体类,用的时候我们要遵循面向接口编程。于是乎就出现了这种情况:
IAnimal animal = new Dog(); animal.Call(); |
哈哈,这不就是面向接口进行编程了吗?当然,这不是面向接口编程,虽然你使用了接口,但不代表就是面向接口编程。我们一起来围观下吧。这段代码通常是写在客户端的,于是乎,客户端既知道接口,又知道了具体的实现类。有何问题?我们还是先来回顾一下接口最主要的作用吧!没错,就是“封装隔离”,具体的实现类被接口所封装并使其与客户端隔离,也就是说,客户端不应该知道具体实现类的存在。如果不能像上面这样做的话,客户端现在只知道一个接口,那我怎么才能够得到接口的对象呢?
二、简单工厂
1.我们先看看简单工厂的定义吧!
定义:提供一个创建对象实例的功能,而无需关心其具体的实现。
2.上述问题的解决方案:
既然我们在客户端中无法直接得到实现类的对象,那我们可以新增一个类,让这个类来帮我们返回实现类的对象,这样,客户端只要和新增的类有关系就行了,就不需要知道具体的类了,这样不就解除了它们之间的耦合了。
3.简单工厂结构图:
IAnimal:定义客户端所需要的功能接口。
Dog:具体实现IAnimal的实现类
Pig:具体实现IAnimal的实现类
Factory:工厂,选择何时的实现类来创建IAnimal接口对象。
Client:客户端,通过Factory来获取IAnimal接口对象。
提示:看完第一章的朋友,看类图就能知道代码的大致实现了。
4.简单工厂示例代码:
class Program { static void Main(string[] args) { Factory factory = new Factory(); IAnimal animal = factory.Create("pig"); animal.Call(); Console.ReadKey(); } } public class Factory { public IAnimal Create(string Name) { if (Name == "pig") { return new Pig(); } else if (Name == "dog") { return new Dog(); } else { return new Pig(); } } } public interface IAnimal { void Call(); } public class Dog : IAnimal { public void Call() { Console.WriteLine("狗在叫........"); } } public class Pig : IAnimal { public void Call() { Console.WriteLine("猪在叫........"); } } |
三、理解简单工厂
1.静态工厂
在客户端中我们创建了工厂类的实例,使用该实例的Create方法,来返回所需要的接口对象。我们其实可以将工厂类当作工具类进行使用,就是将Create方法定义成静态方法,直接通过Factory.Create()获取对象实例,最好也将该类的构造函数设为私有,这样外部就无法实例化该工厂类。静态工厂就是这么来的。
2.如何选择合适的接口对象
工厂类是如何返回合适的接口对象的呢?一般常用的有两种方式,第一种就是从客户端获取参数,通过对参数的判断来选择接口实例,上述的代码示例就是采用此方法,第二种是通过获取配置文件中的值,来选择接口实例。
3.工厂模式的优点
简单工厂模式帮助我们实现了组件的封装,可以让外部实现面向接口编程。
简单工厂模式实现了客户端与具体实现类的解耦。客户端只需知道工厂和接口就可以了。
4.工厂模式的缺点
增加的客户端的复杂度,如果客户端是通过参数获取接口对象的,那么客户端就要理解参数所代表的具体功能。
系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂逻辑过于复杂。
5.何时选用工厂模式
如果想要完全封装隔离具体的实现,让外部只能通过接口来操作封装体,可以选用简单工厂。
如果想要把对外创建对象的职责进行集中管理和控制,可以使用简单工厂。
6.总结
简单工厂的本质就是选择实现,再重点就是“选择”,一般在我们系统中接口和具体的实现类都是已经存在了的,我们要思考的就是“如何选择”实现。
设计模式之工厂方法(三)
前面一篇已经说了简单工厂,那就趁热打铁,开始讲述工厂方法吧。
一、引出模式
开发场景:实现一个导出数据的应用框架,让客户来选择导出数据的方式,并执行真正的数据导出。
导出数据的方式可以是导出到文本文件,导出到XML,导出到Excel,导出到数据库等。
好了,我们大概来想一下思路,对于上面的应用框架,导出来的都是一个数据文件,系统并不知道导出的是哪种文件,所以我们可以定义一个统一的导出数据的接口(IDataExport),对于实现导出数据的业务功能对象来说(Client),它应该根据需求来创建相应的IDataExport的实现对象,可是对于实现导出数据的业务功能对象(Client)而已,它并不知道应该创建哪一个IDataExport的实现对象,也不知道如何创建。
也就是说,客户端是需要创建IDataExport具体的实例对象的,但是客户端只知道IDataExport接口,并不知道具体的实现,那怎么办呢?
二、认识模式
1.模式定义
定义一个创建对象的接口,让子类决定去实例化哪个类,工厂方法使一个类的实例延迟到子类。
2.解决思路
在实现数据导出业务对象(Client)里,我们根本不知道该使用哪种导出方式,因此这个对象就不能和具体数据导出对象耦合在一起,它需面对数据导出的接口,但是接口不能直接使用的,需要用到具体的实现对象的实例。
这样不就矛盾了吗?那怎么办?
说白了,客户端不就是需要一个具体实现类的对象吗,那好我就用个方法来帮你创建这个实例对象,这个方法自己也不知道它将被用来干嘛,我们可以将这个方法定义成抽象方法,它就当成摆设吧,具体的创建实例的工作就交给它的子类吧,那么这个对象本身就可以只是面对接口编程,而无需关系具体类的创建工作。
3.模式结构
IDataExport:定义工厂方法所创建的对象的接口,也就是client需要使用的对象的接口。
ExportToTxt:具体的IDataExport接口的实现对象。
IFactoryMethod:创建器,声明工厂方法,工厂方法通常会返回一个IDataExport类型的实例对象,里面大多是抽象方法。也可以提供默认实现。
CreatForTxt:具体的创建器对象,实现IFactoryMethod定义的工厂方法,返回具体的IFactoryMethod实例。
4.示例代码
class Program { static void Main(string[] args) {
//最关键一步,改变了new的对象,也就改变了具体的实现
IFactoryMethod factory = new CreateForSql();
IDataExport export = factory.Create();
export.Export();
Console.ReadKey();
}
}
#region 工厂
public interface IFactoryMethod
{
IDataExport Create();
}
public class CreateForTxt : IFactoryMethod
{
public IDataExport Create()
{
return new ExportToTxt();
}
}
public class CreateForSql : IFactoryMethod
{
public IDataExport Create()
{
return new ExportToSql();
}
}
public class CreateForXml : IFactoryMethod
{
public IDataExport Create()
{
return new ExportToXml();
}
}
#endregion
#region 产品
public interface IDataExport
{
void Export();
}
public class ExportToTxt : IDataExport
{
public ExportToTxt()
{
}
public void Export()
{
Console.WriteLine("数据导出到txt");
}
}
public class ExportToSql : IDataExport
{
public void Export()
{
Console.WriteLine("数据导出到数据库");
}
}
public class ExportToXml : IDataExport
{
public void Export()
{
Console.WriteLine("数据导出到xml");
}
}
#endregion |
三、理解模式
1.模式功能
工厂方法模式主要功能是让父类不知道具体实现情况下,完成自身的功能调用;而具体的实现则延迟到子类来实现。
2.谁来使用工厂方法创建的对象
事实上,在工厂方法模式里面,应该是IFactoryMethod中的其他方法在使用工厂方法创建的对象,虽然也可以把工厂方法创建的对象直接提供给IFactoryMethod外部使用,但工厂方法的本意,是由IFactoryMethod对象内部的方法来使用工厂方法创建的对象,也就是说,工厂方法一般不提供给IFactoryMethod外部使用。
1)客户端使用IFactoryMethod对象的情况
客户端:
class Program { static void Main(string[] args) { #region 客户端使用IFactoryMethod对象(推荐)
IFactoryMethod factory = new CreateForSql();
factory.export();
#endregion
Console.ReadKey();
}
}
|
抽象工厂类
public abstract class IFactoryMethod { /// <summary> /// 抽象方法 /// </summary> /// <returns></returns> public abstract IDataExport Create();
/// <summary>
/// 依赖注入 setter方法
/// </summary>
public void export()
{
IDataExport export = Create();
export.Export();
}
} |
2)客户端使用由IFactoryMethod创建出来的对象
这比较好理解,示例代码用的就是这种情况
客户端
class Program { static void Main(string[] args) {
#region 客户端使用IFactoryMethod创建的对象(不推荐)
IFactoryMethod factory = new CreateForSql();
IDataExport export = factory.Create();
export.Export();
#endregion
Console.ReadKey();
}
} |
抽象工厂类
public interface IFactoryMethod { IDataExport Create(); } |
小结:在工厂方法模式中,客户端要么使用IFactoryMethod对象,要么使用IFactoryMethod创建的对象,一般客户端不直接使用工厂方法。当然也可以直接把工厂方法暴露给客户端操作,但是一般不这么做。
3.模式优点
1)可以在不知具体实现的情况下编程
工厂方法模式可以让你在实现功能时,如果需要某个产品对象,只需要使用产品的接口即可,而无需关系具体的实现。选择具体的实现的任务延迟到子类去完成。
2)更容易扩展对象的新版本
只需要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
3) 连接平行的类层次结构
4.模式缺点
具体产品对象和工厂方法的耦合
5.模式本质
工厂方法模式的本质就是延迟到子类来选择实现。
与简单工厂的区别:从本质上讲,在具体实现上都是“选择实现”,但是简单工厂是直接在工厂类中进行“选择实现”;而工厂方法会把这个工作延迟到子类来实现,工厂类里面的工厂方法是依赖于抽象而不是具体的实现。
6.模式选择
1) 如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类中去实现。
2) 如果一个类本身希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式。
public interface IFactoryMethod { IDataExport Create(); } |
|