从动物叫三声,谈谈设计模式
作者:肖波
今天在博问中看到一个动物叫三声的设计模式讨论,觉得很有意思,抽空实现了一下,觉得有必要写下来,和大家一起探讨探讨。
原始需求:http://space.cnblogs.com/question/2360/ 我把原文转贴如下:
我有一个动物类,它只有一个“叫三声”的方法,然后猫类与狗类都从它继承而来。现在我想猫应该“喵喵喵”叫,狗应该“汪汪汪”叫,我是用如下代码实现,但,有没有更好的方法,或是一些能用得上设计模式的方法(我是为设计模式而设计模式)。
从这篇博问的回复中,很多意见是使用抽象方法描述 基类 animal 中的shout 方法,不同动物对这个方法进行重载,以达到用不同方式叫的目的。
我谈谈我的设计:
首先我们看到这个需求中有两个重要概念:
一个是叫,还有一个是叫声。
Shout(叫)是一个动作,不同动物有不同的叫声,但只要是动物就会叫(不会叫的动物不在这里讨论,原始需求中并没有要求),另外原始需求中要求叫就是
叫三声,所以怎么叫也规定了。显然把Shout这个方法应该放到基类中。
Sound(叫声)是一个属性,不同动物有不同的叫声,同一个动物不同情况下也可能会发出不同的叫声。叫声不是动作,因为不张口,有声音也出不来。所以这里应该把叫声这个概念单独提取出来作为一个属性。
下面看我的代码:
/// 叫声属性
/// </summary>
[AttributeUsage(AttributeTargets.All)]
class SoundsAttribute : Attribute
{
string _Sounds;
/// <summary>
/// 叫声
/// </summary>
public string Sounds
{
get
{
return _Sounds;
}
}
public SoundsAttribute(string sounds)
{
_Sounds = sounds;
}
}
上面代码是叫声属性,采用属性(Attribute)来描述,而不是特性(Property),可以时程序看起来更优雅。
/// 动物类
/// </summary>
abstract class Animal
{
private SoundsAttribute m_AnimalSounds;
/// <summary>
/// 获取动物的一般叫声
/// </summary>
public Animal()
{
//获取当前对象的类型
Type myType = this.GetType();
//获取一般叫声
foreach (Attribute attr in myType.GetCustomAttributes(true))
{
if (attr is SoundsAttribute)
{
m_AnimalSounds = attr as SoundsAttribute;
}
}
}
/// <summary>
/// 叫
/// </summary>
public void Shout()
{
SoundsAttribute animalSounds = m_AnimalSounds;
//叫三声
if (animalSounds != null)
{
for (int i = 0; i < 3; i++)
{
Console.Write(animalSounds.Sounds);
}
}
Console.WriteLine();
}
}
上面代码是动物类,由于动物本身是一个抽象概念,所以在设计时最好和自然规律吻合,将其设计为抽象类应该是不错的选择。
class Cat : Animal
{
}
[Sounds("汪")]
class Dog : Animal
{
}
猫猫狗狗
{
static void Main(string[] args)
{
//猫
Cat cat = new Cat();
//猫叫三声
cat.Shout();
//狗
Dog dog = new Dog();
//狗叫三声
dog.Shout();
}
}
猫叫三声,够叫三声
结果:
喵喵喵
汪汪汪
写到这边,基本的设计思路和程序实现已经完成。
下面我们来扩展一下需求,看看当前的设计架构是否可以很好的适应需求的变化。
第一,我们希望有个别的动物,叫的时候不是叫三声,而是采取一些特殊的叫法。要做到这点很简单,我们只要将
Animal类中的Shout 方法改为virtual(虚拟)方法,那么派生出来的动物如果不想采用这种方法叫,就可以覆盖这个
虚拟方法,实现其特殊的叫法。
第二,狗在Frenzy(狂怒)时,往往不是发出"汪汪"的叫声,可能是那种低沉的"吼吼"的叫声,现在我们要为狗的狂怒设计一个
方法。由于只是发出的声音不同,我们希望依然使用Animal 类中的Shout方法来发出叫声,但声明Frenzy这个方法采用
"吼"这种叫声。
下面看代码,我们将Shout 方法修改如下:
/// 叫
/// </summary>
virtual public void Shout()
{
SoundsAttribute animalSounds = null;
Type myType = this.GetType();
//获取上一个调用函数
System.Diagnostics.StackFrame stackFrame = new System.Diagnostics.StackFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
//获取特殊叫声
foreach (Attribute attr in methodBase.GetCustomAttributes(true))
{
if (attr is SoundsAttribute)
{
animalSounds = attr as SoundsAttribute;
}
}
//如果没有特殊叫声,用一般叫声叫
animalSounds = animalSounds == null ? m_AnimalSounds : animalSounds;
//叫三声
if (animalSounds != null)
{
for (int i = 0; i < 3; i++)
{
Console.Write(animalSounds.Sounds);
}
}
Console.WriteLine();
}
}
我们再为狗这个类添加一个Frenzy(狂怒)方法
class Dog : Animal
{
/// <summary>
/// 狗狂怒
/// </summary>
[Sounds("吼")]
public void Frenzy()
{
Shout();
}
}
狗要叫了...... 好怕....
Dog dog = new Dog();
//狗叫三声
dog.Shout();
//狗狂叫三声
dog.Frenzy();
结果:
汪汪汪
吼吼吼
下面给出全部完整的代码
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace TestCosole
{
/// <summary>
/// 叫声属性
/// </summary>
[AttributeUsage(AttributeTargets.All)]
class SoundsAttribute : Attribute
{
string _Sounds;
/// <summary>
/// 叫声
/// </summary>
public string Sounds
{
get
{
return _Sounds;
}
}
public SoundsAttribute(string sounds)
{
_Sounds = sounds;
}
}
/// <summary>
/// 动物类
/// </summary>
abstract class Animal
{
private SoundsAttribute m_AnimalSounds;
/// <summary>
/// 获取动物的一般叫声
/// </summary>
public Animal()
{
//获取当前对象的类型
Type myType = this.GetType();
//获取一般叫声
foreach (Attribute attr in myType.GetCustomAttributes(true))
{
if (attr is SoundsAttribute)
{
m_AnimalSounds = attr as SoundsAttribute;
}
}
}
/// <summary>
/// 叫
/// </summary>
virtual public void Shout()
{
SoundsAttribute animalSounds = null;
Type myType = this.GetType();
//获取上一个调用函数
System.Diagnostics.StackFrame stackFrame = new System.Diagnostics.StackFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
//获取特殊叫声
foreach (Attribute attr in methodBase.GetCustomAttributes(true))
{
if (attr is SoundsAttribute)
{
animalSounds = attr as SoundsAttribute;
}
}
//如果没有特殊叫声,用一般叫声叫
animalSounds = animalSounds == null ? m_AnimalSounds : animalSounds;
//叫三声
if (animalSounds != null)
{
for (int i = 0; i < 3; i++)
{
Console.Write(animalSounds.Sounds);
}
}
Console.WriteLine();
}
}
[Sounds("喵")]
class Cat : Animal
{
}
[Sounds("汪")]
class Dog : Animal
{
/// <summary>
/// 狗狂怒
/// </summary>
[Sounds("吼")]
public void Frenzy()
{
Shout();
}
}
class Program
{
static void Main(string[] args)
{
//猫
Cat cat = new Cat();
//猫叫三声
cat.Shout();
//狗
Dog dog = new Dog();
//狗叫三声
dog.Shout();
//狗狂叫三声
dog.Frenzy();
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述