设计模式系列之面向对象特征
今天开始,开更设计模式系列的文章。会举例列举23种设计模式,第一次写设计模式的文章,定位是初级。等完了这个系列应该会写设计模式进阶,到时候再把常使用到的设计模式重新再写一遍。目录结构已经想好了,前两篇写设计模式相关的概念和面向对象六大基本原则,然后是UML画图。后面23篇,一篇对应一个设计模式。一步一步来,本篇第一篇先来梳理面向对象特征相关的概念。
觉得还不错记得加个关注捧个人场,或者有错误者留个言帮忙指正一下,能打个赏捧个钱赏更赞。支持下楼主哦~
封装
封装是把过程和数据包围起来,隐藏具体细节对外提供功能,外界想要使用我只需要调用相应的功能(方法)即可了,对数据的访问只能通过已定义的界面。
继承
继承是一种类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。抽取共性的东西来一个父亲类,孩子只需要继承这个功能即可。
多态
在继承的基础上展开的。定义为父类引用指向子类对象,多态性是指允许不同类的对象对同一消息作出响应。多态性包括编译时多态和运行时多态。 主要作用就是用来将接口和实现分离开,改善代码的组织结构,增强代码的可读性。 在某些很简单的情况下,或许我们不使用多态也能开发出满足我们需要的程序,但大多数情况,如果没有多态,就会觉得代码极其难以维护。
面向对象通过类和对象来实现抽象,实现时诞生了三个重要的特性,也就是由于这三个特性逐渐才衍生出了各种各样的设计模式。
类与类之间主要有6种关系模式,这六种模板写法导致了平时书写代码的不同耦合度。具体如下所列(耦合度依次增强排列):
为了记住类与类之间的关系,总结了一句话方便记忆:《衣(依赖)冠(关联)剧(聚合)组(组合)纪(继承)实(实现)》。
依赖关系
一般而言,依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A,影响又很小。比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,为类B作为参数被类A在某个method方法中使用。在UML类图设计中,依赖关系用由类A指向类B的带箭头虚线表示。 (第三篇再介绍UML)
代码如下所示:
- Class A {}
- Class B {}
依赖关系表现形式一:
A类是B类的某个方法中的变量,则B类可以调用它,代码如下:
- Class B{
- Public void info(){
- Private A a;
- }
- }
注:B有一个info方法,A类作为该方法的变量来使用。A类的生命期,它是当B类的info方法被调用的时候,才被实例化。
依赖关系表现形式二:
A类是作为B类中某个方法的参数或者返回值时,代码如下:
- Class B {
- Public A info( A a){
- Return null;
- }
- }
依赖关系表现形式三:
A类中有一个是静态方法,B类可以调用它
无用多说,A类被B类的一个方法持有。生命期随着方法的执行结束而结束。
如下就是一个简单的例子:
- class Code {
- public void coding(String str) {
- System.out.println("Coding with "+ str +" !");
- }
- }
- class ProgramMonkey {
- public void programAndroid(Code code) {
- code.coding("Java/Android");
- }
- public void programIOS(Code code) {
- code.coding("OC/IOS");
- }
- public void programPHP(Code code) {
- code.coding("PHP");
- }
- }
- public class Main {
- public static void main(String[] args) {
- ProgramMonkey monkey = new ProgramMonkey();
- Code code = new Code();
- monkey.programAndroid(code);
- monkey.programIOS(code);
- monkey.programPHP(code);
- }
- }
ProgramMonkey 依赖于Code ,而这种依赖是通过形式参数传递的,而且关系很弱,但是Code的方法如果改变,会对ProgramMonkey 产生影响(虽然影响很微妙)。一个好处是,我们只关心ProgramMonkey 的功能,你要什么我创建什么给你(我做一个码农搬砖工就行了),而具体里面code的细节,应该是另一个小伙伴的工作(他是程序员,细节他来维护吧)。
关联关系
关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。在java 语言中关联关系是使用实例变量实现的。在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标记。 (第三篇再介绍UML)。单向关联:
- Class A { }
- Class B {
- Public A a;
- }
双向关联:
- Class A {
- Public B b;
- }
- Class B {
- Public A a;
- }
说明:依赖和关联的区别:
①从类的属性是否增加的角度看:
发生依赖关系的两个类都不会增加属性。其中的一个类作为另一个类的方法的参数或者返回值,或者是某个方法的变量而已。
发生关联关系的两个类,其中的一个类必然成为另一个类的属性,而属性是一种更为紧密的耦合,更为长久的持有关系。
②从关系的生命期角度看:
依赖关系是仅当类的方法被调用时而产生,伴随着方法的结束而结束了。
关联关系是当类实例化的时候即产生,当类销毁的时候,关系结束。相比依赖讲,关联关系的生存期更长。
因为,关联关系比依赖关系耦合性强,所以,关联关系用实线,依赖关系用虚线。如下就是一个简单的例子:
- class Code {
- public void coding(String str) {
- System.out.println("Coding with "+ str +" !");
- }
- }
- class ProgramMonkey {
- private Code mCode;
- public ProgramMonkey(Code mCode) {
- this.mCode = mCode;
- }
- //成员变量关联
- public void programAndroid() {
- mCode.coding("Java/Android");
- }
- }
- public class Main {
- public static void main(String[] args) {
- ProgramMonkey monkey = new ProgramMonkey(new Code());
- monkey.programAndroid();
- }
- }
在本例中,使用成员变量mCode作为了ProgramMonkey 的成员属性,且是通过构造方法注入的依赖实例,两者就产生了关联关系。
关联关系经过细化,还存在两种特例:聚合和组合。
聚合关系
聚合关系是关联关系的一种,耦合度强于关联,他们的代码表现是相同的,仅仅是在语义上有所区别:关联关系的对象间是相互独立的,而聚合关系的对象之间存在着包容关系,他们之间是“整体-个体”的相互关系。与关联关系一样,聚合关系也是通过实例变量实现的,但是关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。聚合是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。比如计算机与CPU、公司与员工的关系等,比如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。(第三篇再介绍UML)
- Class B { }
- Class A{
- Public B b;
- A (B b){
- This.b = b;
- }
- }
聚合关系一般使用setter方法给成员变量赋值。如下就是一个简单的例子:
- public class People{
- Car car;
- House house;
- //聚合关系中作为成员变量的类一般使用set方法赋值
- public void setCar(Car car){
- This.car = car;
- }
- public void setHouse(House house){
- This.house = house;
- }
- public void driver(){
- System.out.println(“车的型号:”+car.getType());
- }
- public void sleep(){
- System.out.println(“我在房子里睡觉:”+house.getAddress());
- }
- }
Peoplr has car and house! 或者:
- public class Car{
- private Tyre tyre;
- private Engine engine;
- public void setTyre(Tyre tyre){
- this.tyre=tyre;
- }
- public void setEngine(Engine engine){
- this.engine=engine;
- }
- }
Car has type and engine! 再或者,一个完整的例子:
- class Code {
- public void coding(String str) {
- System.out.println("Coding with "+ str +" !");
- }
- }
- class ProgramMonkey {
- private Code mCode;
- public Code getmCode() {
- return mCode;
- }
- public void setmCode(Code mCode) {//聚合一般会通过set赋值
- this.mCode = mCode;
- }
- public ProgramMonkey() {
- }
- //也是聚合关系
- public void programAndroid() {
- mCode.coding("Java/Android");
- }
- }
- public class Main {
- public static void main(String[] args) {
- ProgramMonkey monkey = new ProgramMonkey();
- monkey.programAndroid();
- }
- }
本例的意思就是说写程序是Monkey的一项技能,Monkey has Code
组合关系
组合是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周期,组合关系是不能共享的。代表整体的对象需要负责保持部分对象和存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生组合关系,由后者排他地负责生命周期。部分和整体的生命周期一样。
表示contains-a的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。是一种更强的聚合关系。部分不能脱离整体存在,并且“部分”单独存在时没有任何意义。如公司和部门的关系,没有了公司,部门也不能存在了;调查问卷中问题和选项的关系;订单和订单选项的关系。在类图使用实心的菱形表示,菱形从局部指向整体。(第三篇再介绍UML)
- Class B { }
- Class A{
- Public B b;
- A (){
- b = new Wings();
- }
- }
如下就是一个简单的例子:
- public class People{
- private Heart heart;
- public People(){
- heart=new Heart();
- }
- }
这里Psople containes heart!且同生共死。
再比如:
- Public class People{
- Soul soul;
- Body body;
- //组合关系中的成员变量一般会在构造方法实例化
- Public People(){
- soul = news Soul();
- body = new Body();
- }
People containes soul and body!(灵魂和肉体)。同生共死。
再比如,一个完整的例子:
- class Code {
- public void coding(String str) {
- System.out.println("Coding with "+ str +" !");
- }
- }
- class ProgramMonkey {
- private Code mCode;
- public ProgramMonkey() {
- mCode = new Code();
- }
- public void programAndroid() {
- mCode.coding("Java/Android");
- }
- }
- public class Main {
- public static void main(String[] args) {
- ProgramMonkey monkey = new ProgramMonkey();
- monkey.programAndroid();
- }
- }
意思就是说想要成为ProgramMonkey程序猿,你必须具备Code基本要求,没有Code,是毛程序员啊(饿死了);我要是转行了,谁也拿不走我的Code技能;我的技能跟随我的生命,我挂了技能也没了,技能没了,我就挂了!所以说为了表示组合关系,常常会使用构造方法来达到初始化的目的。对于外界呢?压根不知道程序猿有Code的存在,外界只知道你有programAndroid()方法,他们可不懂你其实是Code能力。
聚合和组合关系的区分:
这两个比较难理解,重点说一下。聚合和组合的区别在于:聚合关系是“has-a”关系,较强于一般关联,有整体与局部的关系,并且没有了整体,局部也可单独存在。如公司和员工的关系,公司包含员工,但如果公司倒闭,员工依然可以换公司。组合关系是“contains-a”关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。是一种更强的聚合关系。部分不能脱离整体存在。如公司和部门的关系,没有了公司,部门也不能存在了;调查问卷中问题和选项的关系;订单和订单选项的关系。聚合关系表示整体与部分的关系比较弱,而组合比较强;聚合关系中代表部分事物的对象与代表聚合事物的对象的生存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。组合中一旦删除了组合对象,同时也就删除了代表部分事物的对象。
还有个更好的理解方式:
组合:部分与整体是与生俱来的,部分的存在依赖于整体。比如人与人的某个器官,人一出生,器官就在,人死亡,器官也就没了意义。
聚合:你与你的电脑(或者其它物品),电脑是属于你的吧,但是你是一出生就拥有了电脑吗,电脑是某个厂商生产出来的,然后你买过来才成为了你的一部分。你死了以后,电脑也可以送给别人继续用啊!这就不叫做其存亡了,所以这是聚合。
再给个总结:
①构造函数不同
聚合类的构造函数中包含了另一个类作为参数。A的构造函数中要用到B作为参数传递进来。A可以脱离雁群类而独立存在。
组合类的构造函数中包含了另一个类的实例化。表明A在实例化之前,一定要先实例化B,这两个类紧密的耦合在一起,同生共灭。B类是不可以脱离A类而独立存在
②信息的封装性不同
在聚合关系中,客户端可以同时了解A类和B类,因为他们都是独立的
而在组合关系中,客户端只认识A类,根本就不知道B类的存在,因为B类被严密的封装在A中。
③生命周期不同:
聚合是整体与部分之间是可分离的,他们可以具有各自的生命周期,不会因为一方的消失另一方跟着消失。
聚合是整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束。
继承(泛化)关系
泛化关系是一个类(子类、子接口)继承另外的一个类(父类、父接口)的功能,并且可以有自己的新功能,也既是我们所说的继承关系。在java中通过extends来标识。在UML中用一条带空心箭头的实现表示,从子类指向父类,或者子接口指向父接口。(第三篇再介绍UML)
extends的意思是延伸,扩展,继承。从这个词的角度来说,子类应该分为两层意思:
一种是增强原有类的功能,这体现的不是生物界的”父与子”关系.比如我现在拥有一个工具类Tools,现在我想要增强该工具类,按照开闭原则,我定义了
,此时你就不能说UpdateTools是Tools的”孩子”,因为你发现这里的UpdateTools仅仅是增强原有Tools类的功能,作为功能扩展类来的.此时,我们称其为扩展比较合适.
- UpdateTools extends Tools
另一种则就是体现生物界的”父与子”,即子类和父类在某些行为或者属性的表现不一样.这时候,用单词inherit来表示更合适,也就是我们常说的继承的意思.
到现在,相信你已经明白了extends的含义.其实,实际中,我们使用继承的目的就是为了扩展,因此,可不做深究. 如下就是一个简单的例子:
扩展:
- public class Tools{
- public void print(){
- //do
- }
- }
- public class UpdateTools extens Tools{
- public void printError(){
- //do
- |
- }
继承:
- public class Father{
- public void getName(){
- //do
- }
- }
- public class Son{
- public void other(){
- //do
- }
- }
一个完整的例子:
- class Monkey {
- public void run() {
- System.out.println("I can run!");
- }
- }
- class ProgramMonkey extends Monkey{
- public void program() {
- System.out.println("I can Program!");
- }
- }
- public class Main {
- public static void main(String[] args) {
- ProgramMonkey monkey = new ProgramMonkey();
- monkey.run();
- monkey.program();
- }
- }
解释:如上的意思就是说一开始Monkey猴子只会跑,后来进化成了ProgramMonkey程序猿,具备了新的program技能。也就是说程序猿具备了两项技能。
实现关系
实现关系指的是class类实现interface接口(可以使多个接口)。在java中用implements标识,在UML中用一条带空心三角箭头的虚线标识,从类指向实现的接口。(第三篇再介绍UML)
如下就是一个简单的例子:
- Interface A{}
- Class B implements A{}
- Public class Test{
- Public static void main( String args[] ){
- B b = new B();
- }
- }
再比如,一个完整点的例子:
- interface CodeInterface {
- void code();
- }
- class ProgramMonkey implements CodeInterface {
- public void run() {
- System.out.println("I can run!");
- }
- @Override
- public void code() {
- System.out.println("I can coding!");
- }
- }
- public class Main {
- public static void main(String[] args) {
- ProgramMonkey monkey = new ProgramMonkey();
- monkey.run();
- monkey.code();
- }
- }
意思就是说,有一个Code的技能,谁想学习拥有这个技能谁就实现它,我实现了他,我也就具备技能了!
关联、聚合、组合只能配合语义,结合上下文才能够判断出来,而只给出一段代码让我们判断是关联,聚合,还是组合关系,则是无法判断的。
在运用面向对象的思想进行软件设计时,需要遵循的原则一共有7个,他们是:
在软件设计的过程中,设计代码尽可能的复合上述原则。
关于这七大基本原则是一个很深的话题,后续会分析学习七项基本原则。
欢迎关注,欢迎投稿。
点击下方链接,订阅自定义VIew系列专栏。
来源: http://mp.weixin.qq.com/s/Ybr9wuWblZvxDUnY_MRfsQ