设计模式之美:Type Object(类型对象)
索引
意图
允许在运行时动态灵活的创建新的 "类",而这些类的实例代表着一种不同的对象类型。
Allow the flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object.
结构
Type Object 模式包含两个具体类。一个用于描述对象,另一个用于描述类型。每个对象都包含一个指向其类型的指针。
参与者
TypeClass
- 是 TypeObject 的种类。
- 每个种类都会有一个单独的类。
TypeObject
- 是 TypeClass 的实例。
- 代表着一种对象。定义一种对象所包含的属性和行为。
适用性
当以下情况成立时可以使用 Type Object 模式:
- 类的实例需要根据它们的通用属性或者行为进行分组。
- 类需要为每个分组定义一个子类来实现该分组的通用属性和行为。
- 类需要大量的子类或者多种变化的子类甚至无法预期子类的变化。
- 你需要有能力在运行时创建一些无法在设计阶段预测的新的分组。
- 你需要有能力在类已经被实例化的条件下更改一个对象的子类。
效果
- 运行时创建新的类型对象。
- 避免子类膨胀。
- 客户程序无需了解实例与类型的分离。
- 可以动态的更改类型。
相关模式
- Type Object 模式有些类似于 Strategy和 State模式。这三种模式都是通过将对象内部的一些行为代理到外部的对象中。Stategy 和 State 通常是纯行为的代理,而 Type Object 则包含更多个共享数据状态。State 可以被频繁的更改,Type Object 则很少被改变。Strategy 通常仅包含一个职责,Type Object 则通常包含多个职责。
- Type Object 的实现与 Bridge模式中的 Abstraction 和 Implementor 的关系很像。区别在于,客户程序可以与 Type Object 直接协作,而不会直接与 Implementor 进行交互。
- Type Object 有点像 Flyweight一样处理它的对象。两个对象使用相同的 Type Object 可能看起来是使用的各自的实例,但实际是共享的对象。
- Type Object 可以解决多个对象共享数据和行为的问题。类似的问题也可以用 Prototype模式来解决。
实现
实现方式(一):Type Object 的经典介绍。
- TypeClass - Movie
- TypeObject - Star Wars, The Terminator, Independence Day
- Class - Videotape
- Object - John's Star Wars, Sue's Star Wars
namespace TypeObjectPattern.Implementation1
{
public class Movie
{
public string Title { get; set; }
public float RentalPrice { get; set; }
} public class Videotape
{
public Videotape(Movie movie)
{
this.Movie = movie;
} public Movie Movie { get; private set; } public Customer Renter { get; private set; }
public bool IsRented { get; private set; } public void RentTo(Customer customer)
{
IsRented = true;
Renter = customer;
Renter.ChargeForRental(this.Movie.RentalPrice);
}
} public class Customer
{
public string Name { get; set; } public void ChargeForRental(float rental)
{
// pay money
}
} public class Client
{
public void TestCase1()
{
Customer john = new Customer() { Name = "John" };
Customer sue = new Customer() { Name = "Sue" }; Movie starWars = new Movie()
{
Title = "Star Wars",
RentalPrice = ,
};
Movie terminator = new Movie()
{
Title = "The Terminator",
RentalPrice = ,
}; Videotape starWarsVideotapeForJohn = new Videotape(starWars);
starWarsVideotapeForJohn.RentTo(john); Videotape starWarsVideotapeForSue = new Videotape(starWars);
starWarsVideotapeForSue.RentTo(john); Videotape terminatorVideotapeForJohn = new Videotape(terminator);
terminatorVideotapeForJohn.RentTo(john);
}
}
}
实现方式(二):Type Object 在游戏设计中的使用。
想象我们正在制作在一个虚拟角色扮演游戏。我们的任务是设计一些邪恶的怪兽(Monster)来试图杀掉我们的英雄(Hero)。怪兽有着一些不同的属性,例如生命值(Health)、攻击力(Attacks)、图像、声音等,但以举例为目的我们仅考虑前两个属性。
游戏中的每个怪兽都有自己的生命值。生命值从满血开始,每次怪兽被创伤,生命值减少。怪兽会有一个用于描述攻击的字符串,当怪兽攻击英雄时,这个字符串会被显示到用户屏幕上。
游戏设计师告诉我们,怪兽会有不同的品种(Breed),例如:猛龙(Dragon)和巨魔(Troll)。每个怪兽品种都描述了一种怪兽,在一个场景下会有多个同一种的怪兽遍布在地牢(Dungeon)中。
怪兽的品种(Breed)决定的怪兽的起始生命值,比如猛龙(Dragon)的生命值会比巨魔(Troll)的高,以使猛龙更难被杀掉。同时,同一个品种的怪兽的攻击字符串也是相同的。
通过典型的 OO 设计,我们能得到下面这段代码:
namespace TypeObjectPattern.Implementation2
{
public abstract class Monster
{
public Monster(int startingHealth)
{
Health = startingHealth;
} public int Health { get; private set; }
public abstract string AttackString { get; }
} public class Dragon : Monster
{
public Dragon()
: base()
{
} public override string AttackString
{
get { return "The dragon breathes fire!"; }
}
} public class Troll : Monster
{
public Troll()
: base()
{
} public override string AttackString
{
get { return "The troll clubs you!"; }
}
} public class Client
{
public void TestCase2()
{
Monster dragon = new Dragon();
Monster troll = new Troll();
}
}
}
这段代码浅显易懂,使用继承的方式设计类的层级结构。一个 Dragon 是一个 Monster,满足了 "is a" 的关系。每一个怪物的品种都会用一个子类来实现。
如果游戏中有成百上千的怪物种类,则类的继承关系变得庞大。同时也意味着,增加新的怪物品种就需要增加新的子类代码。
这是可以工作的,但并不是唯一的选择。我们可以尝试另外一种架构。
因为变化较多的部分是品种(Breed)的属性配置,包括生命值和攻击字符串。
所以我们可以将品种(Breed)抽取成单独的类,每个怪物类(Monster)包含一个品种类(Breed)。
Breed 类用于定义 Monster 的 "type"。每一个 Breed 的实例描述着一种 Monster 对象的概念上的 "type"。
namespace TypeObjectPattern.Implementation3
{
public class Breed
{
public int Health { get; set; }
public string AttackString { get; set; }
} public class Monster
{
private Breed _breed; public Monster(Breed breed)
{
_breed = breed;
} public int Health
{
get { return _breed.Health; }
} public string AttackString
{
get { return _breed.AttackString; }
}
} public class Client
{
public void TestCase3()
{
Breed dragonBreed = new Breed()
{
Health = ,
AttackString = "The dragon breathes fire!",
};
Breed trollBreed = new Breed()
{
Health = ,
AttackString = "The troll clubs you!",
}; Monster dragon = new Monster(dragonBreed);
Monster breed = new Monster(trollBreed);
}
}
}
Type Object 在这里的优势在于,我们可以定义新的类型的怪物,而不用修改代码。并且可以在运行时动态生成新的对象和修改对象的属性。
参考资料
《设计模式之美》为 Dennis Gao 发布于博客园的系列文章,任何未经作者本人同意的人为或爬虫转载均为耍流氓。
设计模式之美:Type Object(类型对象)的更多相关文章
- Type Object——类型对象
clr会为应用程序使用的每个类型创建一个内部数据结构,这种数据结构称为类型对象. 具有泛型类型参数的类型称为开放类型(open type),CLR禁止构造开放类型的任何实例. 代码引用一个泛型类型时, ...
- 设计模式之美:Object Pool(对象池)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):实现 DatabaseConnectionPool 类. 实现方式(二):使用对象构造方法和预分配方式实现 ObjectPool ...
- object类型对象 ref参数如何理解?
class Program { static void Main(string[] args) { Student stu = new Student { Name = "老王" ...
- list,set中可以存放Object类型对象
List<JSONObject> series = new ArrayList<JSONObject>();
- C#动态设置匿名类型对象的属性
用C#写WPF程序, 实现功能的过程中碰到一个需求: 动态设置对象的属性,属性名称是未知的,在运行时才能确定. 本来这种需求可以用 Dictionary<string, object> 实 ...
- 设计模式之美:Extension Object(扩展对象)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):使用示例结构实现 Extension Object. 实现方式(二):使用泛型实现 IExtensibleObject<T ...
- 设计模式之美:Null Object(空对象)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Null Object 的示例实现. 意图 通过对缺失对象的封装,以提供默认无任何行为的对象替代品. Encapsulate t ...
- 设计模式之美:Role Object(角色对象)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Role Object 的示例实现. 意图 通过明确地附加角色对象到目标对象中,以使对象可以适配不同的客户需求.每个角色对象都代 ...
- 直接修改托管堆栈中的type object pointer(类型对象指针)
都知道.NET是一个强对象类型的框架. 那么对于对象类型又是怎么确定的呢. 最初的我简单认为数据的类型就是定义时字段的类型修饰决定的(回来发现这种观点是绝对错误的) 我们知道引用对象存储在托管堆栈中, ...
随机推荐
- android中将EditText改成不可编辑的状态
今天在做项目的时候,要想实现一个将EditText变成不可编辑的状态,通过查找博客,发现一个好方法,对于单独的EditText控件我们可以单独设置 1.首先想到在xml中设置Android:edita ...
- Archlinux 上 Nginx + PHP + Mariadb + DiscuzX2.5 安装小记
因为不好找下载集成服务器工具,而且他们自己又打包了一份 php 之类的程序,本身系统就已经有 php 还有数据库什么的了再搞一份受不了,最后选择了手动配置…… 这是一个在 Archlinux 上手动从 ...
- 如何让aspnet服务加载静态资源html(我的动态网页静态化) 转
我们知道,IIS自身是不能处理像ASPX扩展名这样的页面,只能直接请求像HTML这样的静态文件. 当客户端请求一个服务器资源时,这个HTTP请求会被inetinfo.exe进程截获(www服务),然后 ...
- odoo10 费用报销
odo10 对费用报销进行了改进,恢复了 8.0 及之前版本具有的 单个报销包含多个 明细内容的功能. 使用步骤大致如下: 根据管理需要设立 相应的科目和分析帐户 科目 分析帐户 建立费用目录 员工录 ...
- Crystal Reports拉报表报错:Error detected by database DLL
问题描述: 最近在使用Crystal Reports打印报表时,提示错误信息:"Error detected by database DLL." 如下图: 经查找,是因为数据库名称 ...
- 深入理解Memcache原理 [转]
1.为什么要使用memcache 由于网站的高并发读写需求,传统的关系型数据库开始出现瓶颈,例如: 1)对数据库的高并发读写: 关系型数据库本身就是个庞然大物,处理过程非常耗时(如解析SQL语句,事务 ...
- C#图片保存到本地
/// <summary> /// 上传微信头像到服务器 /// </summary> /// <param name="imgUrl">< ...
- SaaS模式给用户带来的优势
这两年SaaS服务在中国越来越受欢迎,企业正在从使用本地化软件向SaaS服务转变.由于企业用户人力成本的上升.移动终端设备的兴起以及共享经济对企业的影响,企业采用经营设备.软件的方式也在逐渐发生着变化 ...
- iOS中“返回”操作相关
在程序中,总会设置“返回”按钮,但不可能在每一个控制器中都去设置一次“返回”按钮,那如何设置全局的“返回”按钮呢? 首先自定义一个导航控制器,在tabBarController中添加子控制器时,使用这 ...
- Ubuntu grub引导修复
通过USB启动盘安装系统时将引导程序指定到/dev/sdb1,正常应该是指定到/dev/sdb才是,导致安装之后启动不起来. 重新通过USB启动盘进入试用界面,然后打开终端通过如下操作进行grub引导 ...