OOD-抽象类与接口

抽象类与接口

作为面向对象的四大特性之一,抽象类和接口是经常被使用到的概念。那么抽象类和接口的区别是什么?什么时候用接口?什么时候用抽象类?抽象类和接口存在的意义是什么?

抽象类和接口的区别

有一个抽象类的使用例子(为便于展示将函数声明及定义写在一起):Logger 是一个记录日志的抽象类,FileLoggerMessageQueueLogger 继承 Logger,分别实现两种不同的日志记录方式:记录日志到文件中和记录日志到消息队列中。FileLoggerMessageQueueLogger 两个子类复用了父类 Logger 中的nameenabledminPermittedLevel 属性和 log() 方法,但因为这两个子类写日志的方式不同,它们又各自重写了父类中的 doLog() 方法。

#pragma once
#include<string>
typedef int level;
class Logger
{
private:
	std::string name;
	bool enabled;
	level minPermittedLevel;

public:
	Logger(std::string name_, bool enabled_, level minPermittedLevel_) :
		name(name_), enabled(enabled_), minPermittedLevel(minPermittedLevel_)
	{}
	void log(level level_, std::string message)
	{
		bool loagable = enabled && (minPermittedLevel <= level_);
		if (!loagable)
			return;
		doLog(level_, message);
	}
	virtual void doLog(level level_, std::string message) = 0;
};

class FileLogger : public Logger
{
public:
	void doLog() {...}
};
class MessageQueueLogger : public Logger
{
public:
	void doLog() {...}
};

以上这个例子使用纯虚函数实现了抽象类,可以得到抽象类的特征:
抽象类不允许被实例化,只能被继承。
抽象类可以包含属性和方法。方法既可以包含代码实现(比如 Logger 中的 log() 方法),也可以不包含代码实现(比如 Logger 中的 doLog() 方法)。不包含代码实现的方法叫作抽象方法。
子类继承抽象类,必须实现抽象类中的所有抽象方法。对应到例子代码中就是,所有继承 Logger 抽象类的子类,都必须重写 doLog() 方法。

而接口,对于java可能有更直观的体会,因为java中可以直接使用interferce 关键字定义接口。在Java中定义个接口,之后可以定义不同的类来实现接口,如果有个函数的参数为这个接口的话,就可以对各自的类做出不同的响应,这里使用一个简单的例子:

public interface animal
{
    public void info();
}

public class dog implements animal
{
    public void info()
    {
        System.out.println("dog class");
    }
}

public class cat implements animal
{
    public void info()
    {
        System.out.println("cat class");
    }
}

public class func{
    public void act(animal a)
    {
        a.info();
    }
}
public class test{
    public static void main(String []args)
    {
        dog d = new dog();
        cat c = new cat();

        func f = new func();
        f.act(d);
        f.act(c);

    }
}

而在C++中,没有接口的定义,我们要定义抽象类来实现像java中的接口功能。例如:

#include <iostream>
using namespace std;
class animal
{
public:
    virtual void info()=0;
};
class dog:public animal
{
    void info()
    {
        cout << "dog class" << endl;
    }
};
class cat:public animal
{
    void info()
    {
        cout << "cat class" << endl;
    }
};
void test(animal &a)
{
    a.info();
}

int main()
{
    dog d;
    cat c;

    test(d);
    test(c);

    return 0;
}

由上可以得到接口的特性有:
接口不能包含属性(也就是成员变量)。
接口只能声明方法,方法不能包含代码实现。
类实现接口的时候,必须实现接口中声明的所有方法。

由此可以得到,抽象类实际上就是类,只不过是一种特殊的类,这种类不能被实例化为对象,只能被子类继承。我们知道,继承关系是一种 is-a 的关系,那抽象类既然属于类,也表示一种 is-a 的关系。相对于抽象类的 is-a 关系来说,接口表示一种 has-a 关系,表示具有某些功能。对于接口,有一个更加形象的叫法,那就是协议。

为什么需要抽象类和接口?

为什么需要抽象类?

首先,抽象类不能实例化,只能被继承,因此抽象类是为了解决代码复用问题。当然,使用虚函数也是为了实现多态特性。若 Logger 类中没有设计定义纯虚函数,则子类中无法直接定义doLog()方法。除此之外,使用抽象类,编译器会强制要求子类重写 log() 方法,否则会报编译错误,在大型项目的开发中,方法很多的时候,这样的设计可以确保我们在子类中都会实现对应的log()方法。

为什么需要接口?

抽象类更多的是为了代码复用,而接口就更侧重于解耦。接口是对行为的一种抽象,相当于一组协议或者契约,联想类比 API 接口。调用者只需要关注抽象的接口,不需要了解具体的实现,具体的实现代码对调用者透明。接口实现了约定和实现相分离,可以降低代码间的耦合性,提高代码的可扩展性。
当然前文已经讲了用c++实现接口,即构造满足接口特性(不包含属性、只能声明方法,方法不能包含代码实现)的抽象类。

如何决定使用抽象类还是接口?

如果我们要表示一种 is-a 的关系,并且是为了解决代码复用的问题,我们就用抽象类;如果我们要表示一种 has-a 关系,并且是为了解决抽象而非代码复用的问题,那我们就可以使用接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值