求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
设计模式
 
火龙果软件    发布于 2014-2-27
 

设计模式之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();
}
 
相关文章

UML概览
UML图解:用例图(Use case diagram )
UML图解:活动图(activity diagram )
UML图解:类图(class diagram )
UML图解:对象图(object diagram)
UML图解:顺序图( sequence diagram )
 
相关文档

模型跟踪:跟踪图、矩阵、关系(建模工具EA)
自定义表格(Custom Table)在EA中的使用
元素的详情浏览控制
UAF 1.2规范解读(DMM 和 UAFML )
EA中支持的各种图表
EA中的界面原型建模
 
相关课程

UML与面向对象分析设计
UML + 嵌入式系统分析设计
业务建模与业务分析
基于SysML和EA进行系统设计与建模
基于模型的需求管理
业务建模 & 领域驱动设计
 
分享到
 
 


如何向妻子解释OOD
OOAD与UML笔记
UML类图与类的关系详解
UML统一建模语言初学
总结一下领域模型的验证
基于 UML 的业务建模


面向对象的分析设计
基于UML的面向对象分析设计
UML + 嵌入式系统分析设计
关系数据库面向OOAD设计
业务建模与业务架构
使用用例进行需求管理


某航空IT部门 业务分析与业务建模
联想 业务需求分析与建模
北京航管科技 EA工具与架构设计
使用EA和UML进行嵌入式系统分析
全球最大的茶业集团 UML系统分析
华为 基于EA的嵌入式系统建模
水资源服务商 基于EA进行UML建模
更多...