Head.First.Object-Oriented.Design.and.Analysis《深入浅出面向对象的分析与设计》读书笔记(五)
2010-07-24 19:35 Virus-BeautyCode 阅读(1963) 评论(2) 编辑 收藏 举报
好的设计产生灵活的软件-上(good design = flexible software part 1)
------------没有什么是一成不变的nothing ever stays the same
引言 |
正文 |
先来看一个已有系统的类关系图。就是一个guitar销售商店,可以查询guitar,添加guitar。
客户说最近他想卖mandolins曼陀林(一种琵琶类乐器),想要我们在系统中实现这个功能,并且可以针对这种乐器进行搜索。
我们需要在原有系统中支持曼陀林这种新的乐器。既然是乐器,肯定和guitar有共同的地方,我们可以抽象出来。加入一个乐器类,表示曼陀林和guitar的抽象。
其实在上图中,我们会发现,乐器的细节类和guitar的细节类,以及mandolin的细节类都很相似,还应该有个抽象的乐器细节类。把共同的细节信息都移到抽象类中。
就会变成下面的结果
在UML图中
如上图所示,第一条是关系,也就是通常我们说的类之间的关系,1:1,1:n,n:1,之类的。第二条就是继承。第三条是集成,还不太理解,在后面理解了我们再来讨论他。
由上面最后的两张类图,我们可以写出下面的代码。

using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BeautyCode.Common.ConApp.Head.First.OO.Design
{
public abstract class InstrumentSpec
{
private Builder _builder;
private InstrumentType _type;
private Wood _backwood;
private Wood _topwood;
public InstrumentSpec(Builder builder, InstrumentType type, Wood backwood, Wood topwood)
{
_builder = builder;
_topwood = topwood;
_type = type;
_backwood = backwood;
}
public virtual bool Matches(InstrumentSpec instance)
{
if (instance._builder != _builder)
return false;
if (instance._backwood != _backwood)
return false;
if (instance._topwood != _topwood)
return false;
if (instance._type != _type)
return false;
return true;
}
}
public abstract class Instrument
{
private string _serialNumber;
public string SerialNumber
{
get { return _serialNumber; }
}
private double _price;
public double Price
{
get { return _price; }
}
private InstrumentSpec _spec;
public InstrumentSpec Spec
{
get { return _spec; }
}
public Instrument(string serialNumber, double price, InstrumentSpec spec)
{
_serialNumber = serialNumber;
_price = price;
_spec = spec;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BeautyCode.Common.ConApp.Head.First.OO.Design
{
public class GuitarSpec : InstrumentSpec
{
private int _numString;
public int NumString
{
get { return _numString; }
}
public GuitarSpec(Builder builder, InstrumentType type, Wood backwood, Wood topwood, int numString)
: base(builder,
type, backwood, topwood)
{
this._numString = numString;
}
public override bool Matches(InstrumentSpec instance)
{
if (!base.Matches(instance))
return false;
if (!(instance is GuitarSpec))
return false;
GuitarSpec spec = instance as GuitarSpec;
if (spec._numString != _numString)
return false;
return true;
}
}
public class Guitar : Instrument
{
public Guitar(string serialNumber, double price, GuitarSpec spec)
: base(serialNumber, price, spec)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BeautyCode.Common.ConApp.Head.First.OO.Design
{
public class MandolinSpec : InstrumentSpec
{
private Style _style;
public Style Style
{
get { return _style; }
}
public MandolinSpec(Builder builder, InstrumentType type, Wood backwood, Wood topwood, Style style)
: base(builder,
type, backwood, topwood)
{
this._style = style;
}
public override bool Matches(InstrumentSpec instance)
{
if (!base.Matches(instance))
return false;
if (!(instance is GuitarSpec))
return false;
MandolinSpec spec = instance as MandolinSpec ;
if (spec._style != _style )
return false;
return true;
}
}
public class Mandolin : Instrument
{
public Mandolin(string serialNumber, double price, MandolinSpec spec)
: base(serialNumber, price, spec)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BeautyCode.Common.ConApp.Head.First.OO.Design
{
public class Inventory
{
private List<Instrument> _inventory;
public Inventory()
{
_inventory = new List<Instrument>();
}
public void AddInstrument(string serialNumber,double price,InstrumentSpec spec)
{
Instrument instance = null;
if (spec is GuitarSpec)
{
instance = new Guitar(serialNumber, price,(GuitarSpec ) spec);
}
else if (spec is MandolinSpec)
{
instance = new Mandolin(serialNumber, price, (MandolinSpec)spec);
}
_inventory.Add(instance);
}
public Instrument Get(string serialNumber)
{
return _inventory.Find(i => i.SerialNumber == serialNumber);
}
public List<Instrument> Search(MandolinSpec searchSpec)
{
List<Instrument> instances = _inventory .FindAll(i => i.Spec.Equals(searchSpec));
return instances;
}
}
}
是否这个就满足了要求了呢?现在看来是满足了用户的新要求。
检查软件是否设计良好的一个方法就是加入变化,然后看看它是怎么适应变化的。
现在如果要添加一个新的乐器类型,并且有对应的search方法。需要继承instrument基类,然后要修改inventory类,增加针对新乐器的search方法。当然了,增加新乐器,增加一个他的类和明细类没有问题,肯定是需要的。但是增加新乐器就修改一次inventory类,好像不太应该吧。
让我们来引入一个新的概念:接口。
接口是什么呢?接口是用来定义行为的,定义通用的行为。
假设我们的系统有一个接口,很多类都通过实现这个接口来拥有常用的方法。编程的时候可以使用具体类,也可以使用接口。使用接口可以增加灵活性。你的代码工作在接口之上,就不用每次增加新类型就修改代码了。只要新类型实现接口就可以了。当然了,你也不要叫真,没有关系的,也不用非要实现接口。只是需要具有通用行为的类实现接口就行了。记住,接口是用来定义共同行为的。
在你进行代码工作的任何时候,对类进行操作,你都有两个选择。你可以直接针对子类写代码,也就是FootballPlayer,也可以针对接口IAthlete写代码。当你面临这种抉择的时候,你应该记住:要针对接口编程,不要针对实现编程。
为什么呢?就是为了给你的应用增加灵活性。在针对接口IAthlete编程之后,你的代码可以适用更多类型的athlete。而不用增加一种类型的athlete就为它们写一套重复的代码,给维护带来很大的麻烦。
下面我们来解释一些概念
1、封装
在前面我们已经了解到,封装就是为了减少代码的复制。使用封装避免大量的copy-and-paste。封装还可以使你的类避免不必要的改变。
在你的应用中,如果有的行为可能会发生变化,你就需要将这部分变化从很少变化的代码中移出来。换句话说,也就是封装变化,对可能经常变化的地方进行封装。
2、变化
你已经知道,在软件中唯一不变的就是变化。设计良好的软件很容易应对变化。
最简单的办法就是保持软件的弹性,使得每个类只有一个改变它的原因。
结论 |
OO原则:
- 封装变化
- 针对接口编程,不针对实现编程
- 每一个类应该只有一个改变他的原因
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构