对象的责任:自己万能?还是把责任仍给别的对象?

大家好。
好长时间没联系了。
假期好。

/*************
摘要:
1: 讲个笑话
2: 浅谈面向对象的一些东西
3: 引出一个我没有解决的问题
*****************************************************/


<part 1>

咳咳。本大人来讨论一些很有趣的话题。

先来热身:

Q: 看到如下代码,面向对象的程序员会如何反应?

int calculate(int a, int b, char op)
{
int c;
switch(op) {
case '+':
c=a+b; break;
case '-':
c=a-b; break;
case '*':
c=a*b; break;
case '/':
if (b==0) {
errno=1;
return -1;
} else {
c=a/b; break;
}
}
return c;
}


A: 会说:阿,多么难看的代码。

Java程序员的回答:
abstract class Calculator {
int calculate(int a, int b);
}

class Adder {
int calculate(int a, int b) { return a+b; }
}

class Subtracter {
int calculate(int a, int b) { return a-b; }
}

class Multiplier {
int calculate(int a, int b) { return a*b; }
}

class Divider {
int calculate(int a, int b) { if(b==0) throw new
DivideByZeroException(); else return a+b; }
}

然后,等待文迪学长补充一个C#的吧,记得有一个用Delegate很巧妙地实现的,这里就不敢班门弄斧了。

附注:
然后一位Scheme程序员这样回答:

(define calculate (lambda (a b op) (op a b))) ; 你没看错,就1行代码。感觉脑残的话就忽略吧。


<part 2>

以上我们得出一个结论:
在面向对象的程序设计里面,对象知道自己应该做什么事。而且,不同的派生类的对象,可以对基类规定的共同操作,呈现出不同的反应。这被成为"多态"。

我们来举一个例子:
这是一个C++程序:


// 这是一个图形软件。
typedef pair<int,int> Point;
typedef pair<Point,Point> Rect;

// 我们处理的是一个个的形状
class Shape {
public:
virtual Point getCenter() = 0; // 取得图形的重心。不要忘了,加了"virtual"和"=0"的成员函数成为抽象虚函数。
virtual Rect getBound() = 0; // 取得边框。同样。只要拥有一个抽象函数,这个类就是"纯虚类"。
};

然后,创造两个派生类:
// 第一个是矩形
class Rectangular : public Shape {
private:
int x1,y1,x2,y2; //矩形的左上角和右下角。
public:
virtual Point getCenter() { // 不要忘了,虚函数可以被覆盖。
return Point((x1+x2)/2,(y1+y2)/2);
}
virtual Rect getBound() { // Java里面所有的函数都是虚函数,除非被final。
return Rect(Point(x1,y1),Point(x2,y2));
}
};

// 第二个图形是圆
class Circle : public Shape {
int cx, cy, radius; // 嗯,没说public或者protected,就都是private。
public:
virtual Point getCenter() { // 同样实现
return Point(cx,cy);
}
virtual Rect getBound() {
return Rect(Point(cx-radius/2,cy-radius/2),Point(cx+radius/2,cy+radius/2));
}
};



这样呢,我们有了一个简单的类结构:一个Shape类,还有两个子类,分别是Rectangle和Circle。他们都有getCenter()和getBound()两个方法。

然后呢,给我们一堆Shape,我们逐一对它们进行getCenter()操作即可。不需要知道它们究竟是哪个类的实例。


void printAllCenter(vector<Shape*> vsp) {
for(vector<Shape*>::iterator it = vsp.begin; it!=vsp.end(); ++it) {
// 我记得谁吧这个叫做"Iterator"模式。
Point p = (*it)->getCenter();
cout<<p.first<<" "<<p.second<<endl;
}
}


然后呢?


<part 2.5>

为了在屏幕上显示,我需要给每个类增加"屏幕画图"的方法。
为了兼容以往其它的图形对象,我设立另一个基类:


// Drawable类,表示所有可以在屏幕上画的对象的类。
class Drawable {
public:
virtual void draw(CDC* pDC)=0; //
细心的同学会发现这CDC是MFC里的东西。不过,我好久不用MFC,都已经不会了。现在在研究GTK+。很欢乐的。
// P.S. GTK+的C++绑定叫做GTKmm。
};


然后呢,修改一下原来的类:


class Shape : public Drawable {
....
};


然后,给每个类添加draw()函数呗。


class Rectangle : public Shape {
// 这里有原来的代码.......
virtual void draw(CDC* pDC) {
pDC->rect(x1,y1,x2,y2); //不保证正确。暂且看着理解吧。
}
};

class Circle : public Shape {
// 这里有原来的代码.......
virtual void draw(CDC* pDC) {
pDC->arc(cx,cy,radius, 0.00, 6.28); //暂且当伪代码吧。
}
};


好了。现在所有的图形对象都可以draw()了。我们的面向对象的程序在不断演进,终于到了。。。


<part 2.9>

然后,我又增加了

class Savable {
virtual void save(ostream& ost)=0;
};

这样,图形对象就可以保存到文件里去了。

然后,我又添加了

class UserInteracter {
virtual void onMouseLeftButtonDown()=0;
virtual void onMouseMiddleButtonDown()=0;
virtual void onMouseRightButtonDown()=0;
};

这样,所有的图形对象都可以响应鼠标事件了。

然后,我又添加了

class NetworkClient {
virtual Response request(Request req)=0;
};

以便通过网络与其他程序互动。

然后,我又添加了很多很多的功能。
不管什么功能,都增加给了基类。然后,每个派生类有自己的实现。

结果。。


<part 2.99>


class Circle : public Shape {
virtual Point getPoint() {
/* ..... */
}
virtual Rect getBound() {
/* ..... */
}
virtual void draw(CDC* pDC) {
/* ..... */
}
virtual void save(ostream ost) {
/* ..... */
}
virtual void onMouseLeftButtonClicked() {
/* ..... */
}
virtual void onMouseMiddleButtonClicked() {
/* ..... */
}
virtual void onMouseRightButtonClicked() {
/* ..... */
}
virtual Response request(Request req) {
/* ..... */
}
virtual void aMethod() {
/* ..... */
}
virtual void anotherMethod() {
/* ..... */
}
virtual void yetAnotherMethod() {
/* ..... */
}
virtual void yetYetAnotherMethod() {
/* ..... */
}
virtual void moreMethodHere() {
/* ..... */
}
virtual void yeahTheLastmethod() {
/* ..... */
}
};


到了最后,我拥有了一个巨大的类:Rectangle。这个类里面,实现了从图形计算,到屏幕画图,文件操作,网络操作,GUI操作,数据库操作,分布式操作,内存操作,反病毒操作,软件注册操作,经济操作,金融操作,这个操作,那个操作,渐渐俄,Rectangle成了一个万能的对象。它永远知道自己应该干什么。

但是,我可怜的Rectangle,你只是一个矩形阿。你怎么胜任这么多任务。

我们为你聘请了美术家,数据库分析师,网络工程师,经济人才,社会精英,组成了各个小组。每人负责Rectangle的一部分操作。为了合理利用人才,每个小组横向地负责每一个对象的同一个方法。比如美工小组负责所有的类的draw()方法。

但是,不久引发了灾难


<part 2.999>

每个人都在修改我的源代码。这样我的项目组里,同一个源代码出现了100多个不同版本。我已经无法跟踪,更无从将它们合并在一起了。

我终于忍无可忍,采取了最终的决策:


<part 3.0>


class Shape { // 不实现任何接口(也就是没有任何基类)
public:
virtual Point getCenter() = 0; // Shape专心处理几何运算
virtual Rect getBound() = 0; // 只保留最基本的功能。
};

class Rectangular : public Shape {
private:
int x1,y1,x2,y2;
public:
virtual Point getCenter() { // 嗯
return Point((x1+x2)/2,(y1+y2)/2);
}
virtual Rect getBound() { // 做好本职工作就好了。
}
};

class Circle : public Shape {
int cx, cy, radius;
public:
virtual Point getCenter() { // 乖,
return Point(cx,cy);
}
virtual Rect getBound() { // 你也是一样。
return Rect(Point(cx-radius/2,cy-radius/2),Point(cx+radius/2,cy+radius/2));
}
};


/************** 在另一个文件 drawing.cpp里面 ***************/


#include<typeinfo> // 大家听说过typeinfo这个库吗?没有吧
// 这是在运行时知道对象的继承关系的库。

Rectangle my_rectangle;
Circle my_circle;

void draw(Shape* pShape, CDC* pDC) {
if(typeid(*pShape)==typeid(my_rectangle)) {
Rectangle *pRect = (Rectangle*)pShape;
pDC->drawRect( pRect->x1, pRect->y1, pRect->x2, pRect->y2);
} else if (typeid(*pShape)==typeid(my_circle)) {
Circle *pCircle = (Circle*)pShape;
pDC->arc( ..........);
}
}


所以,最后又成了老式的C语言风格。


终结:

所以,问题来了。
怎么才能既保留面向对象的方法,
又能把功能分散到各自的模块里面呢?

大家讨论一下吧。


我目前的了解:
C#的Partial Class是不是能解决这个问题?
ruby支持"打开一个已有的类,向里面添加一些方法"

如果我是Haskell程序员,我回这样做:

type Point = (Int,Int)
type Rect = (Point, Point)
data Shape | Rect Point Point | Circle Point Int

center :: Shape -> Point
center (Rect (x1,y1) (x2,y2)) = ((x1+x2)/2, (y1+y2)/2)
center (Circle (cx,cy) r) = (cx,cy)


然后增加类(不是面向对象里面的类,是函数式编程的类)
这一切发生在另一个文件里

class Drawable a where
draw :: CDC -> a -> IO ()

instance (Shape a) => Drawable a where
draw dc (Rect (x1,y1) (x2,y2)) = do { DCDrawRect dc x1 y1 x2 y2 }
draw dc (Circle (cx,cy) r) = do { DCDrawArc dc cx cy r 0 6.28 }



======================

一种可行的解法:

感谢Jerry Tian同学。

让save()不再是Shape的方法,而是创建另一套Saver类。

class BaseSaver {
public:
void save(Shape* pShape)=0;
};
BaseSaver::_theOnlyBaseSaverInTheWorld=NULL;

class RectSaver {
public:
void save(Shape* pShape) { ... }
} theOnlyRectSaverInTheWorld; // 哈哈,这就是我的所谓Singleton模式吧。不过没有安全保证。


然后,给Shape类加一个指向Saver类的引用。

class Shape {
BaseSaver* pSaver;
public:
void save() {
pSaver->save();
}
};


对于具体的Shape派生类,指向相应的Saver类:

class Rectangle {
Rectangle() { pSaver = &theOnlyRectSaverInTheWorld } // 确定自己的Saver。
};


做法就是把自己的类的责任转移给别的类。这样,自己和那个类就可以分别编辑了,不用担心多个人一起修改会导致的麻烦。
这是“桥接模式”的一种形式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值