面向对象几大原则

进行面向对象编程,有下面几个原则:
一. 面向抽象原则
二. 开闭原则
三. 多用组合少用继承原则
四. 高内聚-低耦合原则

一. 面向抽象原则

下面首先先介绍抽象类和接口,然后介绍面向抽象编程。

1. 抽象类和接口

1.1 抽象类
抽象类(abstract)具有如下特点:
1. 抽象类中可以有abstract方法,也可以有非abstract方法。
2. 抽象类不能使用new运算符创建对象。
3. 如果一个非抽象类是某个抽象类的子类,那么它必须重写父类的abstract方法,即在子类中将abstract方法重新声明,但必须去掉abstract修饰符,同时要保证声明的方法名字,返回类型,参数个数和类型与父类的abstract方法完全相同。
4. 作为上转型对象。尽管抽象类不能使用new运算符创建对象,但其非abstract子类必须要重写全部abstract方法,这样一来,就可以让抽象类声明的对象成为其子类对象的上转型对象,并调用子类重写的方法。
例如,下面抽象类A中有一个abstract方法add(int x,int y);

public abstract class A {
    public abstract int add(int x, int y);
}

下列B是A的一个非abstract子类,子类B在重写父类A中的abstract方法add(int x,int y)时,将其实现为计算参数x与y的和。

public class B extends A {

    @Override
    public int add(int x, int y) {
        return x + y;
    }
}

假设b是子类B创建的对象,那么可以让A类声明的对象a成为对象b的上转型对象,即让a存放b的引用。上转型对象能够调用子类重写的add()方法,例如:

public class Application {
    public static void main(String[] args) {
        A a;
        a = new B(); // a是B类对象的上转型对象
        int m = a.add(3, 2); // a调用子类B重写的add()方法
        System.out.println(m);// 输出结果为5
    }
}

1.2 接口
接口(interface)具有如下特点:
1. 接口中只可以有public权限的abstract方法,不能有非abstract方法。
2. 接口由类去实现,即一个类如果实现一个接口,那么它必须重写接口中的abstract方法,即将abstract方法重新声明,但必须去掉abstract修饰符,同时要保证声明的方法名字,返回类型,参数个数和类型与接口中的方法完全相同。
3. 接口回调。接口回调是指可以把实现接口的类的对象的引用赋给该接口声明的接口变量中,那么该接口变量就可以调用被类实现的接口中的方法,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象方法的接口回调。
例如,下面接口Com中有一个abstract方法sub(int x,int y);

public interface Com {
    public abstract int sub(int x, int y);
}

ComImp是实现Com接口的类,ComImp类在重写Com接口中的abstract 方法sub(int x,int y)时,将其实现为计算参数x与y的差:

public class ComImp implements Com {

    @Override
    public int sub(int x, int y) {
        return x - y;
    }
}

可以让Com接口声明的接口变量com存放ComImp类的对象引用,那么com就可以调用ComImp类实现的接口中的方法。例如:

public class Application {
    public static void main(String[] args) {
        Com com;
        com = new ComImp(); // com变量存放ComImp类的对象引用
        int m = com.sub(8, 2); // com回调ComImp类实现的接口方法
        System.out.println(m);// 输出结果为6
    }
}

2. 面向抽象

所谓面向抽象编程,是指当设计一个类时,不让该类面向具体的类,而是面向抽象类或者接口,即所设计类中的重要数据是抽象类或接口声明的变量,而不是具体类声明的变量。
以下通过一个简单的例子说明面向抽象编程的思想。
比如,已经有了一个Circle类,该类创建的对象circle调用getArea()方法可以计算圆的面积,Circle类的代码如下:

public class Circle {
    double r;

    Circle(double r) {
        this.r = r;
    }

    public double getArea() {
        return (3.14 * r * r);
    }
}

现在要设计一个Pillar类(柱类),该类的对象调用getVolume()方法可以计算柱体体积,Pillar类的代码如下:

public class Pillar {
    Circle bottom;
    double height;

    Pillar(Circle bottom, double height) {
        this.bottom = bottom;
        this.height = height;
    }

    public double getVolume() {
        return (bottom.getArea() * height);
    }
}

上述Pillar类中,bottom是用具体类Circle声明的变量,如果不涉及用户需求的变化,上面的Pillar类的设计没有任何不妥,但是在某个时候,用户希望Pillar能创建出底是三角形的柱体。显然上述Pillar类无法创建出这样的柱体,即上述设计的Pillar类不应对用户的这种需求。
现在重新来设计Pillar类。首先,注意到柱体计算体积的关键是计算出底面积,一个柱体在计算底体积时不应该关系它的底是怎样形状的具体图形,应该只关心这种图形是否具有计算面积的方法。因此,在设计Pillar类时不应当让它的底是某个具体类声明的变量,一旦这样做,Pillar类就依赖具体的类,缺乏弹性,难以应对需求的变化。
下面将面向抽象重新设计Pillar类。首先编写一个抽象类Geometry(或接口),该抽象类(接口)中定义了一个抽象的getArea()方法。

public abstract class Geometry { // 如果使用接口需要用interface来定义Geometry
    public abstract double getArea();
}

现在Pillar类的设计者可以面向Geometry 类编写代码,即Pillar类应当把Geometry对象作为自己的成员,该成员可以调用Geometry的子类重写的getArea()方法。这样一来,Pillar类就可以将计算底面积的任务指派给实现Geometry类的子类的实例(如果Geometry是一个接口,Pillar类就可以将计算底面积的任务指派给实现Geometry接口的类的实例)。
以下Pillar类的设计不再依赖具体类,而是面向Geometry类,即Pillar类中的bottom是用抽象类Geometry声明的变量,而不是具体类声明的变量。重新设计Pillar类的代码如下:

public class Pillar {
    Geometry bottom; // bottom是抽象类Geometry声明的变量
    double height;

    Pillar(Geometry bottom, double height) {
        this.bottom = bottom;
        this.height = height;
    }

    public double getVolume() {
        return (bottom.getArea() * height); // bottom可以调用子类重写的getArea方法
    }
}

下面Circle类和Rectangle类都是Geometry的子类,二者都必须重写Geometry 类的getArea()方法来计算各自的面积。
Circle.java

public class Circle extends Geometry{
    double r;

    Circle(double r) {
        this.r = r;
    }

    public double getArea() {
        return (3.14 * r * r);
    }
}

Rectangle.java

public class Rectangle extends Geometry{
    double a, b;

    Rectangle(double a, double b) {
        this.a = a;
        this.b = b;
    }

    public double getArea() {
        return a * b;
    }
}

现在,就可以用Pillar类创建出具有矩形底或者圆形底的柱体了,如下列Application.java所示:

public class Application {
    public static void main(String[] args) {
        Pillar pillar;
        Geometry bottom;
        bottom = new Rectangle(12, 22);
        pillar = new Pillar(bottom, 58); // pillar是具有矩形底的柱体
        System.out.println("矩形底的柱体的体积" + pillar.getVolume());
        bottom = new Circle(10);
        pillar = new Pillar(bottom, 58); // pillar是具有圆形底的柱体
        System.out.println("圆形底的柱体的体积" + pillar.getVolume());
    }
}

通过面向抽象来设计Pillar类,使得该Pillar类不再依赖具体类,因此每当系统增加新的Geometry的子类时,比如增加一个Triangle子类,那么不需要修改Pillar类的任何代码,就可以使用Pillar创建出具有三角形底的柱体。

二. 开闭原则

所谓”开闭原则”(Open-Closed Principle)就是让设计对拓展开放,对修改关闭。怎么理解对拓展开放,对修改关闭呢?实际上这句话的本质是指当一个设计中增加新的模块时,不需要修改现有的模块。在给出一个设计是,应该首先考虑到用户需求的变化,将应对用户变化的部分设计为对拓展开放,而设计的核心部分是经过精心考虑过之后确定下来的基本结构,这部分应该是对修改关闭的,即不能因为用户的需求变化而再发生变化,因为这部分不是用来应对需求变化的。如果设计遵守了”开-闭原则”,那么这个设计一定是易维护的,因为在设计中增加新的模块时,不必去修改设计中的核心模块。比如上面代码给出的设计中有四个类,类图如下所示:

该设计中的Geometry和Pillar类就是系统中队修改关闭的部分,而Geometry的子类是对拓展开放的部分。当向系统再增加任何Geometry的子类时(对拓展开放),不必修改Pillar类,就可以使用Pillar创建出具有Geometry的心子类指定的底的柱体。
通常无法让设计的每个部分都遵守”开-闭原则”,甚至不应当这样去做,应当把主要精力集中在应对设计中最有可能因需求变化而需要改变的地方,然后想办法应用”开-闭原则”。
当设计某些系统时,经常需要面向抽象来考虑系统的总体设计,不要考虑具体类,这样就容易设计出满足”开-闭原则”的系统,在程序设计好后,首先对abstract类的修改关闭,否则,一旦修改abstract类,比如,为它增加一个abstract方法,那么abstract类所有的子类都需要做出修改;应该对增加abstract类的子类开放,即在程序中再增加子类时,不需要修改其他面向抽象类而设计的重要类。

三.多用组合少用继承原则

方法复用的两种最常用的技术就是类继承和对象组合

1. 继承和复用

子类继承父类的方法作为自己的一个方法,就好像它们是在子类中直接声明一样,可以被子类中自己声明的任何实例方法调用。也就是说,父类的方法可以被子类以继承的方式复用。
通过继承来复用父类的方法的优点是:
子类可以重写父类的方法,即易于修改或者拓展那些被复用的方法。
通过继承来复用父类的方法的缺点是:
1. 子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
2. 子类和父类的关系是强耦合关系,也就是说当父类的方法的行为更改时,必然导致子类发生变化。
3.通过继承进行复用也称”白盒”复用,其缺点是父类的内部细节对于子类而已是可见的。

2. 组合和复用

一个类的成员变量可以是Java允许的任何数据类型,因此,一个类可以把对象当作自己的成员变量,如果用这样的类创建对象,那么该对象中就会有其他对象,也就是说,该对象将其他对象作为自己的组成部分(这就是人们常说的Has——A),或者说该对象是由几个对象组合而成。
如果一个对象a组合了对象b,那么对象a就可以委托对象b调用其方法,即对象a以组合的方式复用对象b的方法/
通过组合对象来复用方法的优点是:
1. 通过组合来复用方法也称”黑盒”复用,因为当前对象只能委托所包含的对象调用其方法,这样一来,当前对象所包含对象方法的细节对当前对象是不可见的。
2.对象与所包含对象属于弱耦合关系,因为,如果修改当前对象所包含对象类的代码,不必修改当前对象类的代码。
3.当前对象可以在运行时动态指定所包含的对象,例如,假设Com是以恶搞接口,该接口中有一个computer()方法,那么下列Computer类的对象可以在运行时动态指定所包含的对象,即运行期间,Computer类的实例可调用setCom(Com com)方法将其中的com变量存放任何实现Com接口对象的引用。

public class Computer {
    Com com;

    public void setCom(Com com) {
        this.com = com;
    }

    public void f() {
        com.computer();
    }
}

通过组合对象来复用方法的缺点是:
1.容易导致系统中的对象过多。
2.为了能够组合多个对象,必须仔细的对接口进行定义

3. 多用组合,少用继承

之所以提倡多用组合,少用继承,是因为在许多设计中,人们希望系统的类之间尽量是低耦合关系,而不希望是强耦合关系。即在许多情况下需要避开继承的缺点,而需要组合的优点。怎么样合理地使用组合,而不是使用继承来获得方法的复用需要经过一定时间的认真思考,学习和编程实践才能悟出其中的道理。

四. 高内聚-低耦合原则

如果类中的方法是一组相关的行为,则称该类是高内聚的,反之称为低内聚的。搞内聚便于类的维护,而低内聚不利于类的维护。
所谓低耦合就是尽量不要让一个类含有太多其他类的实例引用,以避免修改系统的其中一部分会影响到其他部分。

时间: 2024-08-09 02:01:59

面向对象几大原则的相关文章

公共技术点之面向对象六大原则

概述 在工作初期,我们可能会经常会有这样的感觉,自己的代码接口设计混乱.代码耦合较为严重.一个类的代码过多等等,自己回头看的时候都觉得汗颜.再看那些知名的开源库,它们大多有着整洁的代码.清晰简单的接口.职责单一的类,这个时候我们通常会捶胸顿足而感叹:什么时候老夫才能写出这样的代码! 在做开发的这些年中,我渐渐的感觉到,其实国内的一些初.中级工程师写的东西不规范或者说不够清晰的原因是缺乏一些指导原则.他们手中挥舞着面向对象的大旗,写出来的东西却充斥着面向过程的气味.也许是他们不知道有这些原则,也许

面向对象六大原则-含具体实例

1.优化代码的第一步——单一职责原则 单一职责原则的英文名称是Single Responsibility Principle,简称SRP.它的定义是:就一个类而言,应该仅有一个引起它变化的原因.简单来说,一个类中应该是一组相关性很高的函数.数据的封装.就像秦小波老师在<设计模式之禅>中说的:“这是一个备受争议却又及其重要的原则.只要你想和别人争执.怄气或者是吵架,这个原则是屡试不爽的”.因为单一职责的划分界限并不是总是那么清晰,很多时候都是需要靠个人经验来界定.当然,最大的问题就是对职责的定义

Java程序员应该了解的10个面向对象设计原则

面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorator.Observer这样的设计模式,而不重视面向对象的分析和设计.甚至还有经验丰富的Java程序员没有听说过OOPS和SOLID设计原则,他们根本不知道设计原则的好处,也不知道如何依照这些原则来进行编程. 众所周知,Java编程最基本的原则就是要追求高内聚和低耦合的解决方案和代码模块设计.查看Ap

设计模式2 面向对象设计原则

面向对象设计原则  原则的目的 面向对象设计原创表  单一职责原则案例 开闭原则 案例 依赖倒转原则 案例 面向对象设计原则  对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一.在面向对象设计中,可维护性的复用是以设计原则为基础的.每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结构的设计水平.  面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含

第二章 【面向对象设计原则】

(一)如何衡量软件设计的质量 内聚度: 表示一个应用程序的单个单元所负责的任务数量和多样性.内聚与单个类或者单个方法单元相关.(好的软件设计应该做到高内聚.) 耦合度: 耦合度表示类之间关系的紧密程度.低耦合是指尽量使用抽象耦合,少用具体耦合. 设计原则名称 设计原则简介 重要性 单一职责原则 的职责要单一,不能将太多的职责放在一个类中. ★★★★☆ 开闭原则 软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能.  ★★★★★ 历史替换原则 在软件系统中,一个可

(转载)Java程序员应当知道的10个面向对象设计原则

面向对象设计原则是OOPS编程的核心, 但我见过的大多数Java程序员热心于像Singleton (单例) . Decorator(装饰器).Observer(观察者) 等设计模式,而没有把足够多的注意力放在学习面向对象的分析和设计上面.学习面向对象编程像"抽象"."封装"."多态"."继承" 等基础知识是重要的,但同时为了创建简洁.模块化的设计,了解这些设计原则也同等重要.我经常看到不同经验水平的java程序员,他们有的不知

面向对象五大原则三个基本特征

单一职责原则 对于单一职责原则,其核心思想为:一个类,最好只做一件事,只有一个引起它的变化.单一职责原则可以看做是低耦合.高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因.职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度.通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因. 专注,是一个人优良的品质:同样的,单一也是一个类的优良设计.交杂不清的

七大面向对象设计原则

一.面向对象原则综述 七大原则总脉络图: 二.常用的面向对象设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充. . 三.以下详细分析: (一)单一职责原则(Single Responsibility Principle, SRP) 1.定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中    或者:就一个类而言,应该仅有一个引起它变化的原因. 2.分析:一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于

【小话设计模式】面向对象设计原则

1.单一职责原则 单一职责原则的核心思想就是:系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成.英文缩写SRP  Single Responsibility Principle 单一职责原则-->"高内聚,低耦合",每个类应该只有一个职责,此外只能提供一种功能,而引起类变化的原因应该只有一个.在设计模式中,所有的设计模式都遵循这一原则. 优点: 可以降低类的复杂度: 提高类的可读性,提高系统的可维护性: 变更引起的风险降低. 2.里氏替换原则 里氏