大家好。
好长时间没联系了。
假期好。
/*************
摘要:
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。
};
做法就是把自己的类的责任转移给别的类。这样,自己和那个类就可以分别编辑了,不用担心多个人一起修改会导致的麻烦。
这是“桥接模式”的一种形式。
分享到:
相关推荐
C++支持多种编程范式,包括面向对象、泛型编程、过程化编程等。它是静态类型、编译式、通用的编程语言。 关于"万能库文件"这个概念,通常是指一个包含多个功能模块的库文件。在C++中,库文件一般是以lib为后缀的...
在编程领域,对象池是一种设计模式,用于管理对象的创建和销毁,以提高程序性能,尤其是在频繁创建和销毁对象的场景下。本文将详细讲解如何使用C#语言实现一个通用的对象池,以及它背后的原理和优势。 首先,理解...
福建事业单位招聘:申论万能模板之申论万能对策句.docx
申论:《万能用语及名言警句》.doc
万能五笔输入法是一款在中国深受用户喜爱的汉字输入软件,尤其适合于那些熟悉五笔码的用户。这款输入法以其高效、灵活和广泛的词库而著名,为用户提供了一种快速、准确的打字方式。在本文中,我们将深入探讨万能五笔...
iapp源码:万能宝库.iapp
沈老师讲会员「二」 :会员是增长的万能解药?.pdf
Abb万能密钥:高效涂胶工艺包,便捷选项打造工作站,专业实现涂胶操作,Abb万能密钥,带涂胶工艺包,选项快捷方便,可做工作站--涂胶 ,核心关键词:Abb万能密钥; 带涂胶工艺包; 选项快捷方便; 涂胶工作站。,"Abb万能...
### IT安全知识详解:防御SQL注入攻击 ...这个术语实际上并不指向一个真实的、通用的密码,而是指一种利用编程或设计缺陷进行非法访问的方法,尤其指的是SQL注入攻击的一种形式。 #### SQL注入攻击简介 ...
默纳克全套液晶服务器程序大全:万能液晶程序、刷外呼协议等,含主板等刷机软件及教程,默纳克万能液晶服务器MDKE3程序 默纳克万能液晶服务器全套程序,包含万能液晶程序、刷外呼协议程序、升级底座程序、部分厂家...
排骨工具箱之一:“万能toString”是一个关于Java编程中实用工具类的话题,主要涉及的是如何创建一个能够方便、高效地生成对象toString方法的工具类。在Java开发中,经常需要打印对象的详细信息,以便于调试和理解...
《万能五笔输入法详解及其使用技巧》 万能五笔输入法,作为国内流行的汉字输入法之一,以其高效、灵活的特点深受广大用户的喜爱。它不仅支持五笔编码,还...无论是在工作还是生活中,万能五笔都是值得信赖的输入伙伴。
这款工具的核心特点是在线采集,能够高效地抓取目标网站的数据,并在用户自己的服务器上进行再利用。 【描述】中提到的“快速搭建网站仿站的小工具”是指VIVI万能小偷5.5可以自动化地处理网站结构和内容的复制,...
虽然这个文件标题和描述提及的是"高中英语作文万能句和高级词汇",但我们可以从中提炼出一些适用于英语写作的通用知识点,这些知识点不仅适用于高中生,也对其他层次的学习者有所帮助。 1. **段首句的重要性**:...
8. **故障排查**:如果安装万能网卡驱动后仍无法连接网络,可能需要检查网络设置、物理连接或尝试其他兼容的驱动。有时,通过设备管理器查看和重新启用网络适配器,或者执行系统还原也能解决问题。 9. **备用方案**...
"万能查询"和"万能查询数据库"是IT领域中一种高级的数据检索工具,它提供了灵活、强大的查询功能,适用于各种数据管理场景。在这个系统中,用户可以通过自定义查询语句或者设置查询条件来获取所需的信息。下面将详细...
(3) Set rs = Server.CreateObject("ADODB.Connection") ' 创建数据库连接对象 (4) sql = "SELECT * FROM Manage_User WHERE UserName='" & name & "' AND PassWord='" & encrypt(pwd) & "'" ' 构造SQL查询语句 (5) ...