从面向过程到面向对象

改变思维

曾经在学校学习数据结构课程时,第一节课上,老师就告诉我们:程序=数据结构+算法。这句话对我后来学习数据结构起了很大的作用,积极的作用。

可是后来学到C++面向对象部分时,这句话让我在有些地方怎么也想不通。想了很久之后,我得出了另一个结论,在面向对象程序中,程序=对象&行为。这里我使用&,是为了说明对象与行为是关系的。行为是对象的行为,对象要对它自己的行为负责。

这种思维上的转换在从PO到OO的过程中非常重要,下面我举个例子:

假如使用PO的思维,我们想要实现“兔子走路”和“人走路”,我们会怎么做呢?

首先,写一个“兔子”结构体。struct rabbit{}r;

然后是一个“兔子走路”的算法。void rabbitWalk(rabbit r);

最后对“兔子”使用“兔子走路”算法。rabbitWalk(r);

“人走路”与“兔子走路”类似:

struct human{}h;
void humanWalk(human h);
humanWalk(h);

这就是数据结构+算法的思维。程序“兔子走路”=数据结构“兔子”+算法“兔子走路”。程序“人走路”=数据结构“人”+算法“人走路”。

如果数据结构“人”+算法“兔子走路”会怎样呢?

首先创建一个“兔子”对象class rabbit{};rabbit r;

由于兔子需要“走路”这一行为,因此向类中添加这一行为class rabbit{ void walk(){}};

让“兔子”对象执行它的“走路”行为。r.walk();

同样地,“人走路”的程序可以这样实现:

Class human : public animal
{void walk(){}};
human h;
h.walk();

在这里,是对象调用它自己的行为。两个“走路”行为虽然同名,但是它们是不相关的。由对象自己决定它们调用哪个“走路”行为,而程序员要做的,只是让对象执行它们的“走路”行为。人没有兔子的走路行为,所以让人用兔子走路的方法是不可能的。

面向对象

上文用一个简单的例子说明了从PO到OO的思维转变,下面开始介绍一些OO相对于PO来说所具体的一些特性。网上能查到相关术语的解释,这里不会详细展开。

对象和类

对象是与现实世界相关的一个实例,而类是对对象的抽象。注意,在面向对象中,通常是先有对象再有类的。首先是我们需要一个对象,就把它抽象成一类,丰富它的行为。然后再使用这个类实例化出一个对象来使用。

类不一定是名字,也可以是动词。

比如一只“喜羊羊”是一个对象,抽象出来的羊是一个类。

喜羊羊“从学校走到村长的家”也可以是一个对象,抽象出走路这个类。

消息传递与动态分发

请看这样两行代码

xiyangyang.walk(hua_ban);
xiyangyang.walk(bike);

消息传递是指,当程序想要喜羊羊用滑板走路时,就使用这条语句,给xiyangyang发送一条walk的消息,并带上hua_ban参数。

那么这个消息会怎么处理呢?这个我们不需要关心,因为这是由xiyangyang决定的。这就是动态分发。

封装

PO也有封装的概念,不过OO做得更好。

组合与继承

也许大家对继承比较熟悉,对组合比较陌生。这里把它们放在一起作比较,是想说明它们的能实现的作用相似,但是目的却不同。

当我们想给一个类增加行为时,通常想到的是继承。但我作为,继承最主要的作用是提供统一接口,而在功能扩展方面更推荐使用组合。

比如原本有一个类叫“羊”。但现在需要一个“会说话的羊”的对象,要怎么做呢?

我们很容易就想到一个解决方案:

这是一个继承的例子,就好比这种羊进化出一个新的具有speak行为的品种。

其实它还有一种实现方式,就是利用组合:

如果把继承比喻成进化,那么组合就可以这么解释:可以这么解释。这种羊还是不会说话,但是它得到了一个发声器,利用这个发声器的speak行为替羊说话。

多态

多态是这里面最抽象最难解释的概念,所以这里通过一个故事来说明:

大森林里要举行跑步比赛,小动物们都来的报名。

  • PO:

大象:我是大象,我来报名。 管理员:好的,请你用大象跑步的方式参加比赛。

犀牛:我是犀牛,我来报名。 管理员:犀牛?应该是一种牛吧?请你用牛跑步的方式参加比赛。

小黄牛:我是小黄,我来报名。管理员:小黄是什么?没有“小黄跑步”这种跑步方式,你不能参加跑步比赛。

结局:大象完成了比赛。犀牛中途退赛了,因为牛的跑步方式不适合它。小黄牛没有参赛比赛。

  • OO:

管理员:我不管你是什么动物,也不管你以什么方式跑步。只要你有跑步这种行为,你就可以参加比赛,并且以你自己的跑步方式跑步。

结局:大家都参加了比赛,且以自己的方式完成了比赛。

C++

C++在C的基础做了许多功能上的延伸,但是本文的主题是面向对象,其它方面就不作说明。C++作为一种面向对象语言,必然支持上述的面向对象特性。下面将用例子证明:

对象和类

class animal
{
    string name;
public:
    animal(string n):name(n){}
    void introduce(){cout<<"my name is "<<name;}
    void walk(){cout<<"default walk way"<<endl;}
};

class sheep : public animal
{
public:
    sheep(string n):animal(n){}
    void walk(){introduce();cout<<"walk like a sheep"<<endl;}
};

class human:public animal
{
public:
    human(string n):animal(n){}
    void walk(){introduce();cout<<"walk like a human"<<endl;}
};

void test()
{
    sheep A("xiyangyang");
    human B("xiaohuangren");
    A.walk();
    B.walk();
}

运行结果

my name is xiyangyangwalk like a sheep
my name is xiaohuangrenwalk like a human

在本例中:animal、sheep、human都是类,而是A和B对象。

继承

class animal
{
    string name;
public:
    animal(string n):name(n){}
    void introduce(){cout<<"my name is "<<name;}
    void walk(){cout<<"default walk way"<<endl;}
};

class sheep : public animal
{
public:
    sheep(string n):animal(n){}
    void walk(){introduce();cout<<"I walk like a sheep"<<endl;}
    void speak(){introduce();cout<<"sorry, I can‘t speak"<<endl;}
};

class de_dao_gao_yang : public sheep
{
public:
    de_dao_gao_yang(string n):sheep(n){}
    void speak(){introduce();cout<<"I‘m de dao gao yang, I can speak"<<endl;}
};

void test()
{
    de_dao_gao_yang A("yang da xian");
    A.speak();
    A.walk();
    sheep B("normal sheep");
    B.speak();
    B.walk();
}

运行结果:

my name is yang da xianI‘m de dao gao yang, I can speak
my name is yang da xianI walk like a sheep
my name is normal sheepsorry, I can‘t speak
my name is normal sheepI walk like a sheep

在本例中,A羊通过继承实现了说话功能,B羊是普通羊,没有说话功能。但A和B的走路方式是一样的。

组合

class speaker
{
public:
    void say_a_word(string word)
    {
        cout<<word<<endl;
    }
};

class animal
{
    string name;
public:
    animal(string n):name(n){}
    void introduce(){cout<<"my name is "<<name;}
    void walk(){cout<<"default walk way"<<endl;}
};

class sheep : public animal
{
    speaker *hu_die_jie_fa_sheng_qi;
public:
    sheep(string n, bool can_speak = false)
        :animal(n),hu_die_jie_fa_sheng_qi(NULL)
    {
        if(can_speak == true)
            hu_die_jie_fa_sheng_qi = new speaker();
    }
    ~sheep()
    {
        if(hu_die_jie_fa_sheng_qi != NULL)
            delete hu_die_jie_fa_sheng_qi;
        hu_die_jie_fa_sheng_qi = NULL;
    }
    void walk(){introduce();cout<<"I walk like a sheep"<<endl;}
    void speak()
    {
        introduce();
        if(hu_die_jie_fa_sheng_qi == NULL )
            cout<<"sorry, I can‘t speak"<<endl;
        else
            hu_die_jie_fa_sheng_qi->say_a_word("this is a speaker, hello");
    }
};

void test()
{
    sheep A("xiyangyang", true), B("nuanyangyang");
    A.speak();
    A.walk();
    B.speak();
    B.walk();
}

运行结果

my name is xiyangyangthis is a speaker, hello
my name is xiyangyangI walk like a sheep
my name is nuanyangyangsorry, I can‘t speak
my name is nuanyangyangI walk like a sheep

在本例中,A羊通过组合实现了说话功能,B羊是普通羊,没有说话功能。但A和B的走路方式是一样的。

多态

class animal
{
    string name;
public:
    animal(string n):name(n){}
    void introduce(){cout<<"my name is "<<name;}
    virtual void walk(){cout<<"default walk way"<<endl;}
};

class sheep : public animal
{
public:
    sheep(string n):animal(n) { }
    void walk(){introduce();cout<<"I walk like a sheep"<<endl;}
};

class human : public animal
{
public:
    human(string n):animal(n) { }
    void walk(){introduce();cout<<"I walk like a human"<<endl;}
};
class monkey : public animal
{
public:
    monkey(string n):animal(n) { }
    void walk(){introduce();cout<<"I walk like a monkey"<<endl;}
};
class duck : public animal
{
public:
    duck(string n):animal(n) { }
    void walk(){introduce();cout<<"I walk like a duck"<<endl;}
};
class pig : public animal
{
public:
    pig(string n):animal(n) { }
    void walk(){introduce();cout<<"I walk like a pig"<<endl;}
};

void test()
{
    animal * runner[5];
    runner[0] = new sheep("xi yang yang");
    runner[1] = new human("tai shan");
    runner[2] = new monkey("kong kong");
    runner[3] = new duck("tang lao ya");
    runner[4] = new sheep("zhu ba jie");
    for(int i = 0; i < 5; i++)
        runner[i]->walk();
    for(int i = 0; i < 5; i++)
        delete runner[i];
}

运行结果:

my name is xi yang yangI walk like a sheep
my name is tai shanI walk like a human
my name is kong kongI walk like a monkey
my name is tang lao yaI walk like a duck
my name is zhu ba jieI walk like a sheep

在本例中,runner只是一个animal类型的指针,并不知道它实际指向的是什么动物(只有在开始实例化的时候知道,使用的时候是不知道的),但它仍然会调用具体动物类对应的行为。

面向对象设计原则

clean code的宣传者Uncle Bob提出了面向对象设计的五个原则,简称为SOLID。但在我看来,OO设计最重要的原则就是分析可能发生的变化以及如何响应变化。

SRP - Single responsibility principle(单一职责)

单一职责原则是指,一个类,只有一个原因引起它的改变。

先看这样一个类,它符合单一职责原则吗?

正如上文所说,在评价一个OO的设计是否合理时,一个很重要的前提就是推测变化。是在分析这个设计所对应的需求中,哪些地方是有很有可能发生变化的。没有这个前提的评价都是片面武断的。

在本例中,我们假设对于这个羊类来说,它支持哪些行为是容易发生改变的,它叫的方式也是容易发生改变的,那么这个类至于有两个原因导致它的修改,因此不符合单一职责原则。

我们可以怎么修改这个设计呢?一个很常用的方法就是封装变化。在这里我们把“叫”这个容易发生的行为封装起来,封装成另一个类speaker。这样,当发声的方式改变时,只需要修改speaker类就可以了。

OCP - Open-Closed Principle(开放封闭)

开放-关闭原则是指,对扩展开放而对修改关闭。也就是说,尽量通过不修改代码的方法能够扩展类的功能,这听起来有点奇怪,但如果设计得好,是可以做到的。比如上一个例子中,把发声的方法提取出来,那么就可以扩展发声的方法而不需要改变sheep类了。

在这次例子中,我们的需求又发生了变化。我们想要这只羊发出mou的声音,而其它(比如发声的频率、单调)则保持不变。我们很快想到了解决方案

LSP - Liskov Substitution Principle(李氏代换)

李氏代换是指,在任何可以使用基类的地方,都可以使用派生类代替它。这点似乎是理所当然,既然派生类继承基类,那么基类该有的行为派生类当然会有了,这有什么可说的呢?但是参考上面这个例子,虽然派生类继承了基类的所有行为,但不是说这些继承来的行为对于派生类都是有意义的。比如这个高级发声器,其实它并不支持mie这个行为,因此不满足李氏代换。怎么改进呢?考虑一下这个方案:

其实李氏代换,可以理解为基类对于派生类来说没有多余的接口。我们把mie和mou并列放置,并同时继承speaker,父类只是提供接口,而子类实现行为。

从这个例子可以看出,继承和组合虽然相似,却用法不同。继承通常中为了统一接口,而组合可用于扩展功能。

ISP - Interface Segregation Principle(接口隔离)

接口隔离:即使用客户端特定的细粒度接口。

在定义接口时,我们是定义一个大的接口呢还是定义多个小的接口好呢?在这里,我们推荐后者。

多个小接口也许会造成多个接口难以管理,但它带来的好处远多于它的缺点。

当一个接口很大,需要很多参数时,常常是为了适应多种情况。对于某些用例只用到这几个参数而有些用例只用到那几个。

假如某接口有参数A,B,C,D,E,F,G。但某个应用场景其实只用到A,B,C。那么当接口的参数D部分改变时,其实对是这个用例没有关系的。但用例不得不由于这种情况更改它的调用方式,这样不是合理的。

没有想到合适的例子。

DIP - Dependency Inversion Principle(依赖倒置)

依赖倒置是指,我们要依赖抽象而不是具体实现。因为OO最大的特点就是擅长于响应变化。上文说过,我们要封装变化。那么抽象和具体实现,哪个更容易变化呢?当然是具体实现。

在上面这个例子中,speaker是抽象,mie和mou是实现。sheep现在依赖的是speaker,因此不管我们使用的mie发声法还是mou发声法,对于sheep都没有影响,因此它符合依赖倒置原则。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-26 13:13:00

从面向过程到面向对象的相关文章

面向过程与面向对象的区别-遁地龙卷风

下面例子根据C和Java编写 面向过程与面向对象之间的区别在于认知世界的方式,后者在前者的基础上提供了更高的抽象层次-类. 也就是抽象.封装.继承.多态. 举个例子 输出 小明20岁<=>A,小明打篮球<=>B. 面向过程相当于在类中调用main方法输出A.B public class Test { public static void main( String[] args ) { System.out.println( "小明去上学" ); System.o

面向过程,面向对象,函数式对同一个问题的思考方式

我之所以对函数式代码感兴趣是因为函数式代码富有表现力,可以使用简短.紧凑的代码完成工作,同时能对特定的问题给出优雅的解决方案.现代的编程语言不约而同的朝着面向对象.函数式.动态.解释执行的方向发展,例如Ruby,Swift.而另一些语言则更加强调函数式编程,如F#,Scala,这种语言有着强大的类型推断系统,编写的代码洁程度则令人叹为观止. 在F#编写一个两个数相加的函数,在F# Interactive中输入: let add num1 num2=num1*num2;; F# Interacti

essential C++中关于面向过程和面向对象的说明

昨天在阅读essential C++中看到了一个关于面向过程和面向对象的区别的例子,感觉挺好的.记录下来.... 这个例子是关于照相机的.照相机有三个性质,一个是控制位置:通常使用3个浮点数据来表示其坐标:还有一个视角方向的性质:可以使用3个浮点数据来表示其坐标:最后是一个宽高比的性质,可以使用1个浮点数据来表示. 对于面向过程而言,在编程的过程中势必要不断的在相机的抽象的性质和这7个浮点数据之间反复来回.就好比我们定义一座大楼,面向过程就是要不断在建造大楼的砖头进行打交道,这明显和现实生活的思

面向过程和面向对象的本质理解

http://kiritor.blog.51cto.com/7400479/1226762/ 面向过程和面向对象的本质理解 面向过程就是分析出解决问题所需的步骤,面向对象则是把构成问题的事物分解成对象,抽象 出对象的目的并不在于完成某个步骤,而是描述其再整个解决问题的步骤中的行为. 面向过程的思维方式是分析综合,面向对象的思维方式是构造. 例如c语言解决问题时,一般是先定义数据结构,然后在构造算法.而是用Java面向对象求解时 则是先抽象出对象,构造一个"封闭"的环境,这个环境中有定义

.NET 高级架构师0002 架构师之路(1)---面向过程和面向对象

1.引言     机算机科学是一门应用科学,它的知识体系是典型的倒三角结构,所用的基础知识并不多,只是随着应用领域和方向的不同,产生了很多的分支,所以说编程并不是一件很困难的事情,一个高中生经过特定的训练就可以做得到.但是,会编程和编好程绝对是两码事,同样的程序员,有的人几年之后成为了架构师,有的人却还在不停地coding,只不过ctrl-c.ctrl-v用得更加纯熟了.在中国,编程人员最终的归途无外乎两条:一是转向技术管理,它的终点是CTO:二是继续深入,它的终点是首席架构师,成为CEO的人毕

PHP面向过程和面向对象

php程序编写分为面向过程和面向对象.两者在功能实现上没有区别,但是在代码编写上区别很大,面向过程的代码很乱,不易管理,而面向对象把常用的功能封装为一个类,这样代码清楚多了. 下面举个小例子说明一下: php连接数据库: 面向过程:$conn = mysql_connect('服务器名称', '数据库登陆名', '密码') or die('连接不成功!');          mysql_select_db('库名', $conn) or die('数据库不存在!');          $qu

o&#39;c基础第一天面向过程与面向对象

1. OC将C的复杂的东西简单 a. #import 指令. b. NSLog函数. c. NSString d. BOOL 2. OC在C的基础之上增加了一些面向对象的语法. 面向过程 面向对象. 是解决问题的不同的思路. 1). 将大象放进冰箱. a. 打开冰箱门. b. 把大象放进冰箱 c. 把冰箱门关上. 面向对象. 找到1个冰箱. 只要这个冰箱有自动开门 自动拉近大象 自动关门. 2). 买电脑的需求 a. 根据自己的预算和需求确定型号. 查找资料. b, rMBP 9288 c, 到

面向过程和面向对象编程

 编程方式可分为:面向过程和面向对象 面向过程:面向过程是我们比较原始的编程方式,它是根据计算机的思维方式进行编程,其缺点是当遇到比较复杂的程序时运用这种编程实现起来比较困难.----------面向对象:万物皆对象,现实生活中每个实实在在的东西都可以称之为对象,面向对象它是人类有史以来就具备的思维方式. 例如:我们看到一辆汽车首先我们就会关注它的颜色.大小.价格.款式等等,这些东西称之为属性或者叫做特征.接下来我们就会想到这辆汽车它能做什么,像这种就是可一称之为方法或者叫做行为.而对于我们没个

面向过程和面向对象及面向对象的三大特征

英文及缩写: 面向过程:procedure oriented programming POP 面向对象:object oriented programming OOP 面向对象和面向过程的历程: 面向过程编程采取的是时间换空间的策略,因为在早期计算机配置低,内存小,如何节省内存则成了首要任务,哪怕是运行的时间更长.随着硬件技术的发展,硬件不再成为瓶颈,相反更好的模拟现实世界.系统的可维护性等问题凸显出来,于是面向对象设计应运而生.当下:应用在PC机上的一般应用系统,由于不太需要考虑硬件的限制,而

【C/C++学院】0817-递归汉诺塔 双层递归 /CPP结构体 /面向过程与面向对象的编程模式/类的常识共用体实现一个类的特征/QT应用于类以及类的常识

递归汉诺塔 双层递归 #include <iostream> void han(int n, char A, char B, char C) { static int num = 1; std::cout << "第" << num << "次"; num++; if (n<1) { return; } else { han(n - 1, A, C, B); std::cout << A <&l