首页 > 技术文章 > 小记面向对象

wangsen 2016-01-04 10:41 原文

面向对象与面向过程:

首先我们要明确一定:
无论是面向对象还是面向过程他们都是解决同一个问题,只是方式不同而已。
1.面向过程将程序看作一系列函数的集合,而面向对象将程序看作一种在程序中包含各种独立而又互相调用的对象的集合。

2.面向过程就是分析出解决问题所需的步骤,面向对象则是把问题中存在关键的事物抽取成对象。
抽取对象的目的在于将这些步骤分配到对应的对象中,由对象管理自己的行为。
比如:如何大象装进冰箱?
面向过程:
为了把大象装进冰箱,需要3个过程。
a.把冰箱门打开(得到打开门的冰箱)
b.把大象装进去(打开门后,得到里面装着大象的冰箱)
c.把冰箱门关上(打开门、装好大象后,获得关好门的冰箱)
每个过程有一个阶段性的目标,依次完成这些过程,就能把大象装进冰箱。
面向对象:
为了把大象装进冰箱,需要做三个动作(或者叫行为)。
每个动作有一个执行者,它就是对象。
a.冰箱,你给我把门打开
b.冰箱,你给我把大象装进去(或者说,大象,你给我钻到冰箱里去)
c.冰箱,你给我把门关上依次做这些动作,就能把大象装进冰箱。
3.面向过程要关注每一个步骤的实现细节,关注度比较高,每次都要打开函数去理解每一步的含义。
面向对象不关注实现细节,只会关注这个步骤由哪个对象负责提供,对象对自己提供的服务负责。
4.面向对象编程是一种设计思想,OOP把对象作为程序的基本单元对象包含了属性以及操作这些属性的方法。

5.在程序中,类实际上就是数据类型!例如:整数,小数等等。整数也有 一组特性和行为。
面向过程的语言与面相对象的语言的区别就在于,面向过程的语言不允许程序员自己定义数据类型,
而只能使用程序中内置的数据类型! 而为了模 拟真实世界,为了更好的解决问题,
往往我们需要创建解决问题所必需的数据类型!面向对象编程为我们提供了解决方案。

总结:
我认为两者并不是对立的,而是两者相互促进,oo思想能够帮助我们以符合人类思维的方式进行思考问题,
面向过程的思想可以帮助我们在实现对象中提供的接口时保证逻辑顺序的正确。

对象是什么

面向对象是一种程序的设计思想,可以让我们更符合人们的思维习惯。
面向对象的关键是在于“对象”,那什么是对象呢?
相信大家一定听过“一切皆对象”,这里的对象指的是我们思考问题的目标物体,
比如:动物,地铁,人,车这些都是我们关注的目标物体,也就是我们要思考的对象。
所有程序中存在的对象都是基于业务需求的环境下抽取的,否则将是无边界的抽取,
它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。
所有说对象是面向对象的构建模块,思考和设计的时候都是以对象为基本单元。

对象都有哪些构成

对象是属性和行为集合的载体,从某种程度上说对象必须有属性和对自身属性操作的行为。
经常看到一些存放一堆函数没有任何属性的对象,严格意义上不能称之为对象。
每一个对象都有一种实际的意义,赋予它的职责,对象只对属于自己属性和行为负责,也就我们
常说的“单一职责”。

对象之间通过消息进行交互

对象的存在必然是为了和其他对象进行交互,没有任何交互的对象可以放弃啦。
比如:你踢猫,你是一个对象;你踢的那只猫也是一个对象。你们两个对象之间就是一种交互。
那这种交互又是如何发生的呢?在你踢猫这个例子中,你踢猫是你这个对象使用自身的踢这个行为,
这个行为作用到了猫这个对象身上。猫在被踢后,喵喵叫着跑离开你。
在这里,猫这个对象的叫和跑这两个行为得到了执行。
那试想,是谁执行了这两个行为?显然是猫,但这里与其说是猫,还不如说是你在执行踢行为的过程中执行了猫的叫行为和跑行为。
不是么?难道你踢猫不正是想让猫走开,或者听几声猫的惨叫来取乐?
所以假如你的名字叫Jason,你的猫叫Jack.那么我们可以认为:Jason在踢方法内,调用了Jack的叫方法和跑方法。
用面向对象的记号记作:“Jason.踢(Jack)”调用了“Jack.叫()”和“Jack.跑()”。括号中的Jack是Jason对象踢方法的参数,表示踢行为的作用对象。
用另外一种说法,我们认为:Jason在执行踢方法的过程中,给Jack发送了两个消息以作为命令,Jack收到此两消息后,执行了自己的方法。
这正是对象之间交互的实质所在,也即对象之间通过发送消息来进行交互。

类和对象的关系:

类是描述某些具有共性事物的一个抽象概念,它不是一个客观存在的东西,它就是一个模板。
类可以定义对象
简单说:类是对象的模板,对象是类的实例。
类的抽取过程是从众多对象中提取出相似特征和动作进行封装的,不是凭空捏造的,比如:
小明是一个学生,小花是一个学生,经过我们从这一个个对象中我们发现他们都有相同的属性和行为,
这个时候就可以使用学生类进行承载。思考的过程是先有对象再有类,使用的时候现有类再创建对象。
需要强调一点:每个对象都拥有相同的方法,但各自的数据可能不同。

类都有哪些构成?

一个类通常有哪些元素构成,这些元素都有什么存在价值,换句话我们可以通过这些元素可以干什么。
【类名】描述类的职责,知名达意
类名的好坏直接影响我们对这个类职责的定义,所以最好花点时间去琢磨一下类的命名。
【注释】使用注释说明方法,属性,类职责
【属性】用来表示对象状态
【构造函数】 可以为创建对象提供多种形态的入口,可以初始化对象
构造函数是在对象初始化的时候使用的,通常某个对象在提供自己的对外服务的时候,必须让其他对象传递相关的数据,
在构造函数中进行接受这些数据是经常的做法,如果某些数据是无关紧要的可以不用再对象创建时就提供的建议不要通过构造,
从某种意义上来说构造函数的用途就是为对象获取整个生命周期重要的数据提供一个入口,对外表达一个意义: 你要想使用我(对象),就必须提供给我这些数据,否则我(对象)的服务是不能提供给你的。
比如:数据库持久对象,在使用它提供操作数据服务之前必须要给它提供数据源连接的信息,不然是不可以使用的,具有强制性。
构造函数经常用来:
1.初始化本对象的所有属性。
2.强制用户类提供相关数据,并提供多种创建该对象的方法(构造函数重载)
3.也可以在类初始化的时候做其他的业务操作。

【访问方法 setter】保证访问属性的安全,修改属性保证只有一处
这也是面向对象又一特性"封装"的体现,控制自身属性的安全访问。
【公共方法】 提供类对外的服务
提供公共方法也是类存在的重要职责,一个没有任何对外服务的类可以放弃啦。
类内部提供方法的实现细节,而类的使用者不用关心具体谢姐。
提公共方法的命名一样很重要,
【私有方法】 将不想用户关心的实现,放到私有方法中。
【作用域】 控制属性的访问范围。

怎么创建一个合理的类

1.提供合适的构造函数。
2.让类属性的作用域尽可能小。
3.对类中的元素要合理添加上注释,没有任何注释的类如果命名再不合理就会造成维护理解困难。
4.提供合适的对外接口,这个下面有专门讲。
5.设计的类必然是要和其他的类进行交互的,要么是调用其他的服务,要么本身提供服务,没有这两点的类可以干掉啦。
6.要知道一个原则:类应该只对自己负责
自身状态的变更:
比如:商品的状态由上架,下架。如果要变更商品的状态应该有商品自己去提供变更状态的公共接口。
自身提供的服务:
比如:画图形,方形,圆形,星型它们内容应该是知道如何画出自己的图形。这就是对象负责自己的行为。
7.减少对象之间的依赖
相互依赖的比较少,也就是说一个类的修改不会对其他类产生影响,或者影响很小。
我们经常看到使用hibernate的对象之间关系维护都是对象中直接持有另外一个对象,或集合。
甚至将属于另外一个类职责的行为放到了本类中。
这样的设计会增加对象之间的依赖。

如何以面向对象的方式提供对外接口

这里的对外接口指的是公共的函数。
一.设计接口要使用抽象思维,不体现实现的任何细节。
比如:做出租车去飞机场
出租车提供一个接口,叫做:去目的地就行啦
人只需要使用这个接口告诉出租车目的地就可以啦。不需要人关系有多远,怎么走。。。等细节信息。
这就要求我们在方法的命名上以及注释上都必须保持抽象的思维用自然的语言去描述自己的函数。
二.为用户提供最小的接口,用户关注越小越好
1.以用户需求为驱动编写需要的接口,不要意淫要通过用例图和流程图来确定各个对象需要提供的接口。
比如:笔和笔记本,笔只想笔记本提供空白页让笔书写,
但是笔记本觉得应该给笔提供一个画好的格子页面,这就是自作多情。
2.确定受众用户
注意: 有两种身份关注接口,一种是最终用户(互联网用户),一种是开发者之间的类接口。
互联网用户需要什么服务我们就提供什么接口,以此为驱动开发者,去理清这个服务的所有相关的业务,去抽象,
最终可能抽取出多个对象之间相互通信完成这个业务,其中对象之间的通信也叫接口,只不过这个接口是开发者关注的。
比如:各种service之间的接口它的受众是开发者,module层中的module类就是互联网用户。
3.每个接口都要有明确的职责定义
确定的接口一定是只做一件事,单一的。
比如:下订单是一个对外接口,你不能包含付钱的逻辑。
4.不要轻易修改你的公共接口(服务)
因为你不知道有多少类已经使用了你的这个接口,排查比较复杂

如何利用对象实现系统设计:

一、根据需求抽取出合适的类。
列举需求或者产品原型中出现的所有名词,根据名词列表以及需求描述迭代分析找出真正的类(属性和行为)
二、确定这些类的职责
1.从这个类需要负责的动作中抽象出这个类职责,能够用一句话描述清楚。
2.找出这个类必须的属性。
3.找出这个类必须的操作(只关注类的接口,不会关注实现)
三、确定这些类的关系
根据需求描述使用类去模拟实现的过程,找出对象之间的关系。
对象之间都有哪些关系可以参考另外一个博客:我对uml类图的理解
四、利用建模语言:uml
使用uml的好处就是通过uml可以将你理解的对象关系向别人讲解,也能帮助我们梳理和回顾。

对象的封装:

封装的本质:将不需要对外提供的内容都隐藏起来。
那我怎么做才能算得上封装呢?
1.将对象(类)中的属性全部私有,并提供公有访问该属性的方法(就是咱们通常说的getter(),setter())
控制自身属性对外界的访问。【属性的封装】
2.只提供用户关系的接口,隐藏实现的细节。【方法的封装】
从上面就可以看出对象只封装属于自身内部的属性和行为,所以她是独立不依赖其他对象就可以实现自我管理。【好处】

对象之间的继承:

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
在java中继承的概念不仅限于"extend",其中我们经常使用的实现“implement”接口也是一种继承。
子类和父类的关系一定是:属于的关系即is-a
在java中继承只能是单继承不能多继承但是可以多级继承。
在java判断对象是否存在is-a的关系可以使用关键字:instanceof判断。
特别注意:
1.构造方法不能被继承。
2.方法和属性可以被继承。
3.子类的构造方法隐式地调用父类的无参构造方法。
4.当父类没有不带参数的构造方法时,子类需要使用super关键字来显示调用父类的构造方法。super指的是父类的引用。
5.super关键字必须是构造方法中的第一句。
6.生成子类对象时,Java默认地首先调用父类的不带参数的构造方法,并执行该构造方法,生成父类的对象。
接下来才是调用子类的构造方法,生成子类的对象。(即是想要生成子类对象,必须先生成父类的对象,没有父类的对象就没有子类的对象)
继承在实际的应用:
1.一般不会直接继承一个已经编写好的类,针对已有的类扩展我们都是采用组合的方式,对现有的旧接口进行包装提供新的接口。
2.在模板模式中使用抽象类继承接口,为真正的子类提供共有的默认实现。

对象的多态:

多态给我们带来的好处是消除了类之间的耦合关系,使程序更容易扩展。
Java实现多态有三个必要条件:继承(实现)、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类以及实现关系的接口和实现类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
先有继承关系或者实现关系后才有多态。
其中,不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。
我们需要知道:当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。
多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态。
实际应用:
1.依赖父类或者接口作为方法的参数。
2.依赖的对象如果有继承关系我们就引用父类类型作为变量的数据类型。
方法重载也是一种多态:
可以实现对象对外提供多种形态(不同种类,不同数量的参数)的相同服务。
比如:订单类有一个下订单的接口叫create(),业务需求是:通过商品可以下订单,通过某个活动也可以下定单;
这个时候我们对外暴露接口都是"下订单",我们不想改变下订单这个服务(接口)的定义,我们就可以通过方法的重载去实现。
create(商品) create(活动) 用户可以根据自己的需求去选择使用。

推荐阅读