前两天把图片移动的东西给搞定了,那么就可以开始设计模式了,用的书是《heard first设计模式》,经典不解释。
为了表达设计模式的核心思想,《heard first》用的是控制台输出的方式,我想让效果直观一些,于是用Qt来实现了下。
图片移动的基础请看我的前两篇博客Qt实现图片移动 和 Qt实现图片移动(2)定时器和信号槽
书中的例子是鸭子游戏公司,说是每添加一种鸭子,就要考虑鸭子的fly和quake方法,毕竟不同种的鸭子叫声和飞行方法不一样,有的可能不会飞(木头鸭子)。
为了方便起见,描述一下书上的背景,更方便的是希望你手里能有这本书,电子书下载地址Head First 设计模式(中文完整版+附书源码),感谢这位朋友的分享。
开始的时候,叫Joe的这个哥们给鸭子超类添加了一个方法fly,结果出问题了,看下面的类图:
这是用继承的方式,使用继承,有几个问题:
1、很难知道所有鸭子的飞行行为:父类的fly一般不能满足子类的需求,你说子类覆盖,新的鸭子覆盖fly方法?可以是可以,但是继承的目的很大一部分是想重用代码,dont repeat yourself,而且真的很难知道所有鸭子的飞行行为,例如父类飞行方法是A,子类1飞行方法和子类2的飞行方法都是B,如果用覆盖fly方法的话,难免不repeat yourself,造成代码重复;
2、运行时的行为不容易改变,也就是不能使用多态,这个我不知道怎么讲。
这货后来想用接口,这很对,但是开始的下面想法不对,仅仅是声名了一个借口而已,让子类去实现,明显出现代码重复:
结果被骂了:
开窍后:
最终的uml图,
策略模式结构:
ok,背景介绍完毕(终于完了),现在用Qt来实现:
首先来看两个基类,一个是cat,一个是runnable,见名知意,于前两篇博客不同的是,因为把动作这个概念给抽象出了一个类,所以与移动相关的东西就与cat没有关系了,可以明显的看到cat类少了很多用于移动的员和方法,而runnable专门负责运动,貌似是有个原则叫做“单一职责”,一个类的职责不该过多。
首先是cat.h
1 #ifndef CAT_H
2 #define CAT_H
3
4 #include <QtGui>
5 #include "runnable.h"
6 class Cat : public QWidget
7 {
8 Q_OBJECT
9 public:
10 explicit Cat(QWidget *parent = 0);
11 virtual ~Cat();
12 void setRunnable(Runnable *r);
13 protected:
14 void mousePressEvent(QMouseEvent *event);
15 void paintEvent (QPaintEvent *event);
16 virtual void init()=0;
17 protected:
18 Runnable* runnable;
19 QPixmap* catImg;
20 QWidget* parentWidgetPointer;
21 int shrinkMultiple;
22 };
23
24 #endif // CAT_H
解释:Runnable* runnable;就是跑的接口,而QWidget* parentWidgetPointer;这个变量是我没办法加的,因为当runnable重绘的时候是选择重绘那个部件,而运动了之后貌似只有重绘窗口部件才能连续运动,这个不是很明白,求好心人解释,当时做的时候没有这个window的指针,图片重绘并没有实现连续移动的效果,调用了repaint函数没错,我在用qDebug函数的时候输出了一下重绘的指针,发现此时重绘的是一个Qwidget指针,而只有重绘QWindow的时候,图片才连续移动。茫然啊。。。
cat.cpp
1 #include "cat.h"
2 #include "runright.h"
3 #include "rundown.h"
4
5 Cat::Cat(QWidget *parent) :
6 QWidget(parent),shrinkMultiple(2)
7 {
8 parentWidgetPointer = parent;
9 }
10 void Cat::setRunnable(Runnable *r)
11 {
12 if(!runnable)
13 {
14 runnable = r;
15 }
16 else
17 {
18 Runnable *old = runnable;
19 runnable = r;
20 delete old;
21 }
22 }
23 void Cat::mousePressEvent(QMouseEvent *event)
24 {
25 runnable->mousePressEvent(event);
26 }
27 void Cat::paintEvent (QPaintEvent *event)
28 {
29 QPainter painter(this);
30 painter.drawPixmap(*runnable->getRect (),*catImg);
31 }
32
33 Cat::~Cat()
34 {
35 if(catImg)
36 {
37 delete catImg;
38 }
39 if(runnable)
40 {
41 delete runnable;
42 }
43
44 }
于之前博客不同的是,这次移动图片使用的runnable,因此图片的rect信息我存在runnable里面了,所以画图的时候使用了这么一句
painter.drawPixmap(*runnable->getRect(),*catImg);
剩下的没什么好说的,接下来是runnable.h
1 #ifndef RUNNABLE_H
2 #define RUNNABLE_H
3
4 #include <QtGui>
5
6 class Runnable : public QWidget
7 {
8 Q_OBJECT
9 public:
10 explicit Runnable(QRect *rect,int speed,int time,
11 QWidget *window,QWidget *parent=0);
12 virtual ~Runnable();
13 void mousePressEvent(QMouseEvent *event);
14 QRect* getRect();
15
16 protected:
17 QTimer *timer;
18 QRect *rect;
19 QWidget *window;
20 int speed;
21 int time;
22 bool stop;
23 signals:
24 void runSignal(QRect *rect);
25 void clicked();
26 public slots:
27 void run();
28 void timerTurnSlot();
29 virtual void runSlot(QRect *rect)=0;
30
31 };
32
33 #endif // RUNNABLE_H
有一个纯虚函数的槽的槽,是让子类实现的,将runnable设置为接口。
virtual void runSlot(QRect*rect)=0;
看runnable.cpp的实现
1 #include "runnable.h"
2
3 Runnable::Runnable(QRect *rect,int speed,int time,
4 QWidget *window, QWidget *parent) :
5 QWidget(parent)
6 {
7
8 stop = true;
9 this->window = window;
10 this->speed = speed;
11 this->time = time;
12 timer = new QTimer(this);
13 this->rect = new QRect(rect->topLeft (),rect->bottomRight ());
14 connect( this, SIGNAL( clicked() ), this, SLOT( timerTurnSlot() ) );
15 connect( this, SIGNAL( runSignal(QRect *) ), this, SLOT( runSlot(QRect *)) );
16 connect( timer,SIGNAL(timeout()),this,SLOT(run()));
17 }
18
19 QRect* Runnable::getRect()
20 {
21 return rect;
22 }
23 void Runnable::mousePressEvent(QMouseEvent *event)
24 {
25
26 if (rect->contains (event->x(),event->y()))
27 {
28 stop = !stop;
29 emit clicked();
30 }
31 }
32 void Runnable::run()
33 {
34 emit runSignal(rect);
35 }
36 void Runnable::timerTurnSlot ()
37 {
38 if(!stop)
39 {
40 timer->start (time);
41 }
42 else
43 {
44 timer->stop ();
45 }
46
47 }
48 Runnable::~Runnable()
49 {
50 delete timer;
51 delete rect;
52 }
于之前不同的是,这次我的信号槽里面有参数,当然可以没有,使用之前的方式就是,不过由于对Qt接触不多,顺便学习了一下使用参数的信号槽。
有参数的时候,信号与槽的参数类型要对应,但定时器里面的timeout()这个方法默认就没有参数,因此我加了一个run()的槽来发射runSignal,这样子有参数的信号和槽就能对应起来了。
接下来看子类的实现,rundown,意思是图片向下跑,直接把俩文件贴上:
rundown.h
1 #ifndef RUNDOWN_H
2 #define RUNDOWN_H
3 #include <QtGui>
4 #include "runnable.h"
5 class RunDown : public Runnable
6 {
7 public:
8 explicit RunDown(QRect *rect,int speed,int time,
9 QWidget *window,QWidget *parent=0);
10 ~RunDown();
11 public slots:
12 void runSlot(QRect *rect);
13 };
14
15 #endif // RUNDOWN_H
rundown.cpp
1 #include "rundown.h"
2
3
4 RunDown::RunDown(QRect *rect,int speed,int time,
5 QWidget *window,QWidget *parent):
6 Runnable(rect,speed,time,window,parent)
7 {
8 }
9
10 void RunDown::runSlot(QRect *rect)
11 {
12
13 int height = rect->height ();
14 this->rect->setY (rect->y () + speed);
15 this->rect->setHeight (height);
16 window->repaint ();
17 }
18 RunDown::~RunDown()
19 {
20 }
调用父类构造函数,重写runSlot这个槽即可。
cat子类DownCat意思是向下跑的猫
1 #ifndef DOWNCAT_H
2 #define DOWNCAT_H
3 #include "cat.h"
4 class DownCat : public Cat
5 {
6 public:
7
8 DownCat(QWidget *parent);
9 ~DownCat();
10 protected:
11 void init();
12 };
13
14 #endif // DOWNCAT_H
1 #include "downcat.h"
2 #include "rundown.h"
3 DownCat::DownCat(QWidget *parent):
4 Cat(parent)
5 {
6 init ();
7 }
8
9 void DownCat::init()
10 {
11 catImg = new QPixmap(":/img/black_cat.jpg");
12 int width = catImg->width ()/shrinkMultiple;
13 int height = catImg->height ()/shrinkMultiple;
14 runnable = new RunDown(new QRect(10,10,width,height),
15 5,50,parentWidgetPointer,this);
16 }
17 DownCat::~DownCat()
18 {
19
20 }
多态的实现就是init里面的这句,也正是因为多态,图片运动方式可以很容易的改变,比如加一个mousePressEvent,右键点击的时候将runnable变为RunRight,额,这个时候别忘了delete之前的东西,否则会泄露的。
runnable = new RunDown(new QRect(10,10,width,height),
5,50,parentWidgetPointer,this);
代码分享到了我的百度网盘:策略模式Qt实现图片移动版
贴一贴图片吧,动的效果静态图片真心看不住来,有什么办法么、、
开始的时候
点击俩猫后,上面的猫右移,下面的猫左移: