聊聊面向对象的几个基本原则

一. 面向抽象原则

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

1. 抽象类和接口

1.1 抽象类abstract方法,也可以有非abstract方法。new运算符创建对象。abstract方法,即在子类中将abstract方法重新声明,但必须去掉abstract修饰符,同时要保证声明的方法名字,返回类型,参数个数和类型与父类的abstract方法完全相同。abstract子类必须要重写全部abstract方法,这样一来,就可以让抽象类声明的对象成为其子类对象的上转型对象,并调用子类重写的方法。

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 接口public权限的abstract方法,不能有非abstract方法。abstract方法重新声明,但必须去掉abstract修饰符,同时要保证声明的方法名字,返回类型,参数个数和类型与接口中的方法完全相同。

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. 面向抽象

所谓面向抽象编程,是指当设计一个类时,不让该类面向具体的类,而是面向抽象类或者接口,即所设计类中的重要数据是抽象类或接口声明的变量,而不是具体类声明的变量。

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类不应对用户的这种需求。

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

现在Pillar类的设计者可以面向Geometry 类编写代码,即Pillar类应当把Geometry对象作为自己的成员,该成员可以调用Geometry的子类重写的getArea()方法。这样一来,Pillar类就可以将计算底面积的任务指派给实现Geometry类的子类的实例(如果Geometry是一个接口,Pillar类就可以将计算底面积的任务指派给实现Geometry接口的类的实例)。

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()方法来计算各自的面积。

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

0?wx_fmt=png

该设计中的Geometry和Pillar类就是系统中队修改关闭的部分,而Geometry的子类是对拓展开放的部分。当向系统再增加任何Geometry的子类时(对拓展开放),不必修改Pillar类,就可以使用Pillar创建出具有Geometry的心子类指定的底的柱体。

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

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

1. 继承和复用

子类继承父类的方法作为自己的一个方法,就好像它们是在子类中直接声明一样,可以被子类中自己声明的任何实例方法调用。也就是说,父类的方法可以被子类以继承的方式复用。

2. 组合和复用

一个类的成员变量可以是Java允许的任何数据类型,因此,一个类可以把对象当作自己的成员变量,如果用这样的类创建对象,那么该对象中就会有其他对象,也就是说,该对象将其他对象作为自己的组成部分(这就是人们常说的Has——A),或者说该对象是由几个对象组合而成。

public class Computer {
    Com com;

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

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

通过组合对象来复用方法的缺点是:

3. 多用组合,少用继承

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

四. 高内聚-低耦合原则

如果类中的方法是一组相关的行为,则称该类是高内聚的,反之称为低内聚的。搞内聚便于类的维护,而低内聚不利于类的维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值