设计模式

从今天开始开启设计模式专栏,我会系统的分析和总结每一个设计模式以及应用场景。那么首先,什么是设计模式呢,作为一个软件开发人员,程序人人都会写,但是写出一款逻辑清晰,扩展性强,可维护的程序就不是那么容易做到了。现实世界的问题复杂多样,如何将显示问题映射到我们编写的程序中本就是困难重重。另一方面,软件开发中一个不变的真理就是“一切都在变化之中”,这种变化可能来自于程序本身的复杂度,也可能来自于客户不断变化的需求,这就要求我们在编写程序中一定要考虑变化的因素,将变化的因素抽离出来,设计出一款低耦合的程序就成了程序设计中的难点。

设计模式就是用来解决以上的这些问题的一种方法。应用好设计模式,可以让我们免受程序设计中的变化的苦恼。从设计初至今,设计模式不是一成不变的,也并不是库,它比库更加高级,而且并不是用来解决某一种特定的问题,而是广泛的面向我们在程序开发中遇到的困境。在我们学习完一门语言的相关语法后,我相信设计模式应该作为紧接着学习的重点,它会让你对相关语法的认识更加深刻。比如说java中的接口就是一例,接口是让java流行的重要原因,但是初学完接口的使用,我们并不能很好的掌握接口,只有应用好设计模式,接口才能够大放异彩,我们也才能够真正的体会到接口给面向对象设计带来的魔力。实际上,在很多流行的开源库或者框架中,我们可以看到大量的使用设计模式的例子。

在网络中,有关于设计模式的讲解不在少数,在这里我并不会引出复杂的定义,而是以例子作为基础,道出我们在软件开发中面临的问题,然后引出设计模式,看看设计模式是如何帮助我们解决这些问题的。在之后的讲解中,我都会以java为主要语言,因为它的语法足够简单,使用其他语言作为主语言的开发者触类旁通即可。

最后,关于学习设计模式,希望大家能看一些经典的书籍,不要以其他网路上的博客(包括我的)做抓手学习。毕竟别人嚼过的不香,只有自己品味才能真正理解。书籍最经典的莫过于四人组(经常简称为GoF,即Group of Four)的《设计模式:可复用面向对象软件的基础》,初学可以看《Head First设计模式》,另有一本《大话设计模式》是国人写的评价也很高,同样适合初学者。

啰嗦了这么多,接下来开始步入正题。

设计模式之一策略模式

现在的你,在一家游戏开发公司供职。某一天,你的上司让你开发出一套简单的游戏,游戏中有多个角色,每个角色可以使用武器进行攻击。你打算先以角色入手,很快你设计出了以下的模型:

但是很快,你就发现这样设计并没有一开始想象的那么美好,因为你的上司让你给角色新增一个聊天功能,这时候问题出现了。

你很快想到因为所有角色都继承于Role,所以给Role添加一个聊天方法是理所应当的:

但是这个时候,你的同事告诉你不要这样做,因为他之前设计了一些怪物模型,同样继承于Role类,很明显,怪物也可以使用武器战斗,但是他们不能参与聊天。

你很郁闷,你的同事继承了你的类却没有告诉你,不过你告诉他不用大惊小怪,只要在怪物中重写聊天方法,让他们什么都不做就好了:

但是你的同事告诉你不要逗他,因为他写的怪物少说已经有了二十几个,你不能残忍的让你的同事挨个去重写chat方法。

当你们正在商讨解决方案时,你的上司告诉你,现在角色的武器太单一了,也就是说你不能把所有的战斗实现都放在父类中,我们需要更加多样化的武器。

你很苦恼,但是作为有经验的开发人员,你很快想到可以采用接口去实现,将所有的动作都抽象成一个个接口,让子类实现需要的接口,这样可以解决上司的需求和同事的问题:

但是,这一次是你自己将自己否定了,因为你的角色设计已经进行了很多,你不想将每个角色的方法都重新实现一遍,而且,这些武器很多都是重复的,作为受过良好的OO设计课程的开发人员,你不会强迫自己去写这些重复的代码。最重要的是,你的开发经验告诉你,这些武器很可能会发生改变,如果这样设计,当发生变化时,你就不得不去检查每一个角色类,并修改其中的代码。同样的,你的同事也同意你的看法。

设计原则

现在,你迫切的希望有一种设计模式能够解救你于水火之中。不过在揭晓这个设计模式之前,我们先回到原点,看一下我们遇到的问题到底是由什么原因造成的。

我们可以看到,继承不能很好的解决我们的问题,因为角色的行为是不断发生变化的,他们使用不同的武器和技能,并且你的上司很可能会不断地要求你添加新的行为。接口看起来不错,但是接口不具有实现代码,无法做到代码的复用,这意味着如果你想将某一种行为做统一的变化,就需要一个一个类去检查方法的实现。

要解决这个问题,首先我们需要明确一些设计原则,掌握了这些设计原则,我们才能更好的运用设计模式,解决问题。

面对以上的情况,我们有一个原则正好适用,那就是“封装变化”:

找出应用中可能需要变化的地方,把它们独立出来,不要和那些固定的代码混在一起。

那么对于当前的应用来讲,战斗以及聊天功能都是变化的功能,我们就应该将他们独立出来,分别设计战斗功能和聊天功能。接下来就是如何实现这两个功能了。这又会用到一个设计原则,那就是“接口编程”:

针对接口编程,而不是针对实现编程。

相信这个原则大家都非常清楚,我就不多讲了,接下来直接上代码,这里我只分析战斗功能,其他功能也是类似的:

设计实现

首先是接口设计:

 public interface IFight {
void fight();
}

这个接口很简单,只是提供了fight方法,接下来我们实现几个用各种武器战斗的类:

 public class FightUseAxe implements IFight {
@Override
public void fight() {
System.out.println("使用斧子战斗");
}
}
===============================================
public class FightUseBlade implements IFight {
@Override
public void fight() {
System.out.println("使用剑战斗");
}
}
===============================================
public class FightUseKnife implements IFight {
@Override
public void fight() {
System.out.println("使用匕首战斗");
}
}

注意上面的代码不能写在一个同一个文件中。

接下来我们对角色类进行重构:

 public abstract class Role {

     private IFight weapon;

     public void fight() {
weapon.fight();
} public void setWeapon(IFight weapon) {
this.weapon = weapon;
} public abstract void display();
}

父类中我们实现了fight()方法,做到了统一管理,但是具体方法的实现实际上是交给子类去完成了,也就是IFight属性的赋值是在子类中完成的。接下来我们来看其中一个子类:

 public class King extends Role {
@Override
public void display() {
System.out.println("显示国王的样子");
} public static void main(String[] args) {
Role role = new King();
role.display();
role.setWeapon(new FightUseAxe());
role.fight();
role.setWeapon(new FightUseBlade());
role.fight();
}
/**
* 运行结果:
* 显示国王的样子
* 使用斧子战斗
* 使用剑战斗
*/
}

我们可以看到,通过将战斗独立出来,我们实现了使用者(King)和武器的松耦合,即我们可以动态的改变角色使用的武器。同样的对于其他的角色也是一样,实际上,这是一种方法的委托,角色类不再实现战斗的具体方式,而是交给了成员属性IFight去执行。从另一方面来说,这是一种组合的概念,它和继承不同的地方在于,角色的战斗方式并不是继承而来的,而是和适当的战斗类来组合而成。在这个实例中,我们可以明显的看到组合的优势明显大于继承。

同样,这是一个很重要的设计原则,“多用组合,少用继承”。

其实,从更加抽象的角度来讲,例子中的战斗方式实际上是一种算法,通过策略模式,我们分离了使用算法的角色和算法之间的联系。因此我们可以给出策略模式的定义:

策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

总结

今天我们学习了第一个设计模式即策略模式,当我们面对一些程序中可能会频繁变化的部分时可以采用此模式,它让算法的实现独立于使用算法的客户。同时我们还学习了三个面向对象设计中的重要原则:

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,而不针对实现编程

最后,为了巩固我们的学习成果,下面我们思考这样一个问题。在一片草原中,一群游牧民族养了一群羊和一群马,也就是说在这个草原中有三种生物,分别是人、马、羊、他们的动作有吃(eat)和睡(sleep),不过吃和睡的方式不同,马是站着吃站着睡,羊是站着吃趴着睡,人是坐着吃躺着睡。那么我们该怎样设计该OO模型,在实现功能的基础上,达到低耦合的要求,满足不断变化的需求呢?

[design-patterns]设计模式之一策略模式的更多相关文章

  1. 乐在其中设计模式(C#) - 策略模式(Strategy Pattern)

    原文:乐在其中设计模式(C#) - 策略模式(Strategy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 策略模式(Strategy Pattern) 作者:webabc ...

  2. 设计模式:策略模式(Strategy)

    定   义:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化, 不会影响到使用算法的客户. 示例:商场收银系统,实现正常收费.满300返100.打8折.......等不同收费 ...

  3. PHP设计模式之策略模式

    前提: 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查 找.排序等,一种常用的方法是硬编码(Hard Cod ...

  4. JavaScript设计模式之策略模式(学习笔记)

    在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选 ...

  5. JavaScript设计模式之策略模式

    所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...

  6. 【设计模式】【应用】使用模板方法设计模式、策略模式 处理DAO中的增删改查

    原文:使用模板方法设计模式.策略模式 处理DAO中的增删改查 关于模板模式和策略模式参考前面的文章. 分析 在dao中,我们经常要做增删改查操作,如果每个对每个业务对象的操作都写一遍,代码量非常庞大. ...

  7. 设计模式入门,策略模式,c++代码实现

    // test01.cpp : Defines the entry point for the console application.////第一章,设计模式入门,策略模式#include &quo ...

  8. 设计模式之策略模式和状态模式(strategy pattern & state pattern)

    本文来讲解一下两个结构比较相似的行为设计模式:策略模式和状态模式.两者单独的理解和学习都是比较直观简单的,但是实际使用的时候却并不好实践,算是易学难用的设计模式吧.这也是把两者放在一起介绍的原因,经过 ...

  9. python设计模式之策略模式

    每次看到项目中存在大量的if else代码时,都会心生一丝不安全感. 特别是产品给的需求需要添加或者更改一种if条件时,生怕会因为自己的疏忽而使代码天崩地裂,哈哈,本文的目的就是来解决这种不安全感的, ...

随机推荐

  1. 词向量之word2vec实践

    首先感谢无私分享的各位大神,文中很多内容多有借鉴之处.本次将自己的实验过程记录,希望能帮助有需要的同学. 一.从下载数据开始 现在的中文语料库不是特别丰富,我在之前的文章中略有整理,有兴趣的可以看看. ...

  2. 网站日志流量分析采集(LuaJIT系统环境部署-node03,相关jar包自己手动上传)

    注:/usr/local/src 是源码包路径,可以自己更改 服务器中安装依赖 yum -y install gcc perl pcre-devel openssl openssl-devel 上传 ...

  3. Linux 第十天

    十三.权限管理 1.ACL权限开启 1)dumpe2fs -h /dev/sda3查看分区ACL权限是否开启 -h:仅显示超级块中信息,而不显示磁盘块组的详细信息 2)mount -o remount ...

  4. w9 Ansible批量管理与维护

    Ansible是2013年推出的一种通用自动化工具,可用于配置管理或工作流程自动化.配置管理是一种“基础架构代码”实践,它将事物编码,例如应该在系统上安装什么包和版本,或者应该运行什么守护进程.工作流 ...

  5. JAVA 8 主要新特性 ----------------(二)版本中数据结构的修改浅析

    一.版本中数据结构的修改浅析1.HashMap.HashSet.ConcurrentHashMap的数据结构发生变化 (1)HashMap简介(结构:哈希表+链表) HashMap存储的数据是无序的, ...

  6. Unity3D中声音播放

    Unity3D 播放声音需要使用 Audio Source 组件,并且需要 Audio Listener 组件配合,不然无法听到声音.Main Camera 会默认有 Audio Lisetener. ...

  7. Linux使用MentoHust联网线上校园网, 回到普通有线网络却连不上?

    我的解决方法如下: 在有线网的设置(Wired Settings)那里, 弹出Network窗口, 点击右下角的设置图标, 选择Security, 然后关掉802.1x Security. 然后就可以 ...

  8. FreeRTOS的内存管理

    FreeRTOS提供了几个内存堆管理方案,有复杂的也有简单的.其中最简单的管理策略也能满足很多应用的要求,比如对安全要求高的应用,这些应用根本不允许动态内存分配的. FreeRTOS也允许你自己实现内 ...

  9. UniDBGrid增加显示记录数的label及隐藏refresh按钮

    1. 在UniDBgrid的extEvent属性中写入以下代码: function OnAfterCreate(sender){ var toolbar=sender.getDockedItems() ...

  10. MySQL备份---lvm snapshot

    正常安装(缺点要锁表) 1, 创建一个LV(逻辑卷) , 把MySQL的数据目录放到这个LV上 /var/lib/mysql 对这个LV做快照, 从快照备份数据 删除快照 非正常安装 1,创建LV 2 ...