设计原则-接口隔离原则

接口隔离原则

今天,我们继续讲解 软件设计中的另外一个重要的原则,接口隔离原则。

接口隔离原则**(英语:interface-segregation principles, 缩写:ISP)**

指明客户(client)不应被迫使用对其而言无用的方法或功能。

  • 接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口(role interfaces)

  • 接口隔离原则(ISP)的目的是系统解开耦合,从而容易重构,更改和重新部署。

不应强迫使用者依赖于它们不用的方法。

No client should be forced to depend on methods it does not use.

看上去 这个原则比较容易理解,即 不要放置 使用不到的方法. 站在使用者,或者客户端的角度来考虑 就非常合理了. 设计接口 和使用接口 要分离出来. 对于设计接口来说,尽可能 设计的接口 使用者都需要,并且都会需要, 这样的设计 才是比较好的设计.

如果接口中设计了一些 使用者 不需要的接口,那么对使用者来说,必须要实现 我不需要的功能,这就是 接口的设计者 没有很好的考虑 使用者 到底需要什么样的方法。 从而导致 "胖接口"的产生。

接口隔离原则 用来解决 所谓 “胖接口” 的问题。 就是 接口中定义了很多抽象的方法,但这些方法 可能对于不同客户端来说 有些方法,可能根本不需要来实现。 导致客户端(接口的使用者)拥有了一些他们根本都不需要的方法。这就是 “胖接口” 带来的问题。

银行系统举例

下面有一个银行系统 ,假设有一个业务 就是 存款,取款,转账 这三种业务 。

那么开始抽象接口 如下:

from abc import ABCMeta
from abc import abstractmethod


class TransactionRequest(metaclass=ABCMeta):

    def __init__(self, its_type=None):
        self.its_type = its_type

    def get_type(self):
        return self.its_type
        pass

    @abstractmethod
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    @abstractmethod
    def get_withdraw_amount(self):
        """
        获取取款 金额
        :return:
        """
        pass

    @abstractmethod
    def get_transfer_amount(self):
        """
        获取转账金额
        :return:
        """
        pass

那么对应 三种 操作 对应 三种 handler .

class TransactionHandler:

    @abstractmethod
    def handle(self, request: TransactionRequest):
        pass


class DepositHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_deposit_amount()
        # ...
        return amount
        pass


class WithdrawHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_withdraw_amount()
        # ...
        return amount

        pass


class TransferHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_transfer_amount()
        # ...
        return amount
        pass

对于这三种 交易请求 要实现 这个 “胖 接口” , 要实现 定义的 三个抽象方法。

但是问题是, 对于取款的业务,只需要 get_deposit_amount 方法,而不想或者不需要实现 转账,存款的方法。 但是 已经继承了接口就要实现啊。 好就实现另外的两个 用不到的方法。

代码 就如下:

# -*- coding: utf-8 -*- 


# 具体实现类
class DepositRequest(TransactionRequest):

    
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        print('get_deposit_amount executed ...')
        return '10'
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_withdraw_amount(self):
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_transfer_amount(self):
        pass


class WithdrawRequest(TransactionRequest):

    # 不需要的方法,被迫实现了其他的接口
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    def get_withdraw_amount(self):
        print('get_withdraw_amount executed ...')
        return 1000
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_transfer_amount(self):
        pass


class TransferRequest(TransactionRequest):

    # 不需要的方法,被迫实现了其他的接口
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_withdraw_amount(self):
        pass

    def get_transfer_amount(self):
        print('get_transfer_amount executed ...')
        return 100
        pass


    
handlers = {
    'deposit': DepositHandler(),
    'withdraw': WithdrawHandler(),
    'transfer': TransferHandler()

}


def application(request):
    handler = handlers.get(request.get_type())
    if handler:
        amount = handler.handle(request)
        print(f'amount:{amount}')
    else:
        print(f"Unknown request type! type:{request.get_type()!r}")


if __name__ == '__main__':
    # request = DepositRequest('deposit')
    # request = WithdrawRequest('withdraw')
    request = TransferRequest('transfer')
    application(request)
    pass

看起来 好像 也没有啥问题, 根据不同的请求 分配 不同的handler 然后 处理对应的业务。

其实 TransactionRequest 的子类 只要实现 具体 某个业务 就可以了, 对于取款业务 ,我只想 实现 get_withdraw_amount 就可以了。

对于转账业务 , 我只要实现 get_transfer_amount 就可以了。

接口变动会遇到的问题

假设现在有一个新的需求 需要获取生活缴费接口

首先,我要在TransactionRequest 添加一个新的抽象方法, 然后之前的子类 也都要实现这个原本不需要的方法。

然后在自己实现一个 生活缴费的 handler .

下面备注 的一些 需要改动的地方, 同时 要把之前实现的子类 DepositRequestWithdrawRequestTransferRequest 在之前的三个类中 要添加 get_living_payment_amount 这个不需要的方法,来实现这个不需要的抽象方法。 然后再添加 一个新的子类 LivingPaymentRequest ,然后自己 只需要实现一个方法,即可,但是被迫 实现了四个方法。

代码 具体如下:

# -*- coding: utf-8 -*-

from abc import ABCMeta
from abc import abstractmethod


class TransactionRequest(metaclass=ABCMeta):

    def __init__(self, its_type=None):
        self.its_type = its_type

    def get_type(self):
        return self.its_type
        pass

    @abstractmethod
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    @abstractmethod
    def get_withdraw_amount(self):
        """
        获取取款 金额
        :return:
        """
        pass

    @abstractmethod
    def get_transfer_amount(self):
        """
        获取转账金额
        :return:
        """
        pass

    # new add 新增方法
    @abstractmethod
    def get_living_payment_amount(self):
        """
        获取生活缴费金额
        :return:
        """
        pass


# 具体实现类 都要增加这个方法
class DepositRequest(TransactionRequest):

    def get_deposit_amount(self):
        """
        获取存款金额
        """
        print('get_deposit_amount executed ...')
        return '10'
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_withdraw_amount(self):
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_transfer_amount(self):
        pass

    # add new
    # 不需要的方法,被迫实现了其他的接口
    def get_living_payment_amount(self):
        pass


class WithdrawRequest(TransactionRequest):

    # 不需要的方法,被迫实现了其他的接口
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    def get_withdraw_amount(self):
        print('get_withdraw_amount executed ...')
        return 1000
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_transfer_amount(self):
        pass

    # add new
    # 不需要的方法,被迫实现了其他的接口
    def get_living_payment_amount(self):
        pass


class TransferRequest(TransactionRequest):

    # 不需要的方法,被迫实现了其他的接口
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_withdraw_amount(self):
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_transfer_amount(self):
        print('get_transfer_amount executed ...')
        return 100
        pass

    # add new
    # 不需要的方法,被迫实现了其他的接口
    def get_living_payment_amount(self):
        pass


# add  new 实现类.
class LivingPaymentRequest(TransactionRequest):

    # 不需要的方法,被迫实现了其他的接口
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_withdraw_amount(self):
        pass

    # 不需要的方法,被迫实现了其他的接口
    def get_transfer_amount(self):
        print('get_transfer_amount executed ...')
        return 100
        pass

    def get_living_payment_amount(self):
        print('get_living_payment_amount executed ...')
        return 50
        pass


class TransactionHandler:

    @abstractmethod
    def handle(self, request: TransactionRequest):
        pass


class DepositHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_deposit_amount()
        # ...
        return amount
        pass


class WithdrawHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_withdraw_amount()
        # ...
        return amount
        pass


class TransferHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_transfer_amount()
        # ...
        return amount
        pass


# add new
class LivingPaymentHandler(TransactionHandler):

    def handle(self, request: TransactionRequest):
        amount = request.get_living_payment_amount()
        # ...
        return amount
        pass


handlers = {
    'deposit': DepositHandler(),
    'withdraw': WithdrawHandler(),
    'transfer': TransferHandler(),
    # add new
    'living_payment': LivingPaymentHandler(),

}


def application(request):
    handler = handlers.get(request.get_type())
    if handler:
        amount = handler.handle(request)
        print(f'amount:{amount}')
    else:
        print(f"Unknown request type! type:{request.get_type()!r}")


if __name__ == '__main__':
    # request = DepositRequest('deposit')
    # request = WithdrawRequest('withdraw')
    # request = TransferRequest('transfer')
    request = LivingPaymentRequest('living_payment')
    application(request)
    pass

这样的代码 就是破坏了很多的原则 比如 OCP 原则, 之前在子类中添加了一个不需要的方法,这是非常糟糕的设计。 比如对于 TransferRequest 来说, 我仅仅想知道 get_transfer_amount ,其他的操作 我并不是很关心, 当然 我也不想实现。

这个原因 就是由于 我继承了一个比较 “胖的接口”, 这个胖的接口,看起来功能非常强大,但是 对于接口使用者 只想使用 其中 一个或者两个方法, 但是被迫 把这个接口其他的功能 也要实现一下,即使是空的实现,也要实现一下,因为这是接口约定。。。 这个接口设计 显然是不合理的。

更好的接口设计方法

如何 修改 才会好一点呢?

其实主要的问题就是这个接口 TransactionRequest 这个抽象类 设计了太多的功能 ,接口"太胖了"。 所以要从这个接口下手 进行修改。

我们要把这个胖的接口 进行拆分, 把一组相近的功能 拆成一个接口,这里相近的功能 是由于业务的需要,有一些业务 只需要一个接口, 有的业务可能需要两个接口,都有可能, 这里我拆成 每个业务只有一个接口的情况。

整理代码如下:

# -*- coding: utf-8 -*-
from abc import ABCMeta
from abc import abstractmethod


# 接口层
class TransactionRequest(metaclass=ABCMeta):

    def __init__(self, its_type=None):
        self.its_type = its_type

    def get_type(self):
        return self.its_type


# 接口层 分成三个接口
class DepositRequestInterface(TransactionRequest):
    
    @abstractmethod
    def get_deposit_amount(self):
        """
        获取存款金额
        """
        pass


class WithdrawRequestInterface(TransactionRequest):

    @abstractmethod
    def get_withdraw_amount(self):
        """
        获取取款 金额
        :return:
        """
        pass


class TransferRequestInterface(TransactionRequest):

    @abstractmethod
    def get_transfer_amount(self):
        """
        获取转账金额
        :return:
        """
        pass


# add new
class LivingPaymentRequestInterface(TransactionRequest):
    
    @abstractmethod
    def get_living_payment_amount(self):
        """
        获取生活缴费金额
        :return:
        """
        pass


# 具体实现类,实现对应 接口的方法即可。
class WithdrawRequest(WithdrawRequestInterface):

    def get_withdraw_amount(self):
        print('get_withdraw_amount executed ...')
        return 1000
        pass


# 具体实现类
class DepositRequest(DepositRequestInterface):

    def get_deposit_amount(self):
        """`
        获取存款金额
        """
        print('get_deposit_amount executed ...')
        return 150
        pass


# 具体实现类
class TransferRequest(TransferRequestInterface):

    def get_transfer_amount(self):
        print('get_transfer_amount executed ...')
        return 100
        pass


# 具体实现类,实现 get_living_payment_amount
class LivingPaymentRequest(LivingPaymentRequestInterface):
    def get_living_payment_amount(self):
        print('get_living_payment_amount executed ...')
        return 50
        pass


# 接口层
class TransactionHandler(TransactionRequest):

    @abstractmethod
    def handle(self, request: TransactionRequest):
        pass


class DepositHandler(TransactionHandler):

    def handle(self, request: DepositRequestInterface):
        amount = request.get_deposit_amount()
        # ...
        return amount
        pass


class WithdrawHandler(TransactionHandler):

    def handle(self, request: WithdrawRequestInterface):
        amount = request.get_withdraw_amount()
        # ...
        return amount
        pass


class TransferHandler(TransactionHandler):

    def handle(self, request: TransferRequestInterface):
        amount = request.get_transfer_amount()
        # ...
        return amount
        pass


# add new
# 新增业务处理方法
class LivingPaymentHandler(TransactionHandler):

    def handle(self, request: LivingPaymentRequestInterface):
        amount = request.get_living_payment_amount()
        # ...
        return amount
        pass


handlers = {
    'deposit': DepositHandler(),
    'withdraw': WithdrawHandler(),
    'transfer': TransferHandler(),
    # add new
    'living_payment': LivingPaymentHandler(),

}


def application(request):
    handler = handlers.get(request.get_type())
    if handler:
        amount = handler.handle(request)
        print(f'amount:{amount}')
    else:
        print(f"Unknown request type! type:{request.get_type()!r}")


if __name__ == '__main__':
    request1 = DepositRequest('deposit')
    request2 = WithdrawRequest('withdraw')
    request3 = TransferRequest('transfer')
    request4 = LivingPaymentRequest('living_payment')

    requests = [request1, request2, request3, request4]

    for req in requests:
        application(req)
    pass

这样修改后 ,把原来的 TransactionRequest这个胖接口 ,改成了三个接口。 然后每个接口中都有自己相对独立的方法。 然后在 具体的子类中实现对应的方法即可。 这样接口 划分的更小,更细.

如果此时需要新增一个业务 ,只要定义一个新的接口 LivingPaymentRequestInterface , 然后实现 这个接口 即可. 对原来的代码 几乎不用做什么改变,只是新增方法. 对原来的代码,原来的三个业务 几乎没有任何更改. 这就是 粒度更小的接口,带来的好处.

调皮一下,来理解ISP

想到一个网上的笑话,调皮一下。 接口隔离原则其实就是这样。

识别对象的不同角色,设计小接口

这就像 给女生找对象的6个建议:

  1. 找个能让你笑的男人。
  2. 找个有稳定工作的男人。
  3. 找个喜欢做家务的男人。
  4. 找个诚实的男人。
  5. 找个和你在性方面能契合的男人。
  6. 不要让他们五个人见面。

使用这样的原则,接口设计 就不容易设计的太胖, 哈哈哈,有点调皮啦!

总结

接口隔离原则(ISP),它告诉我们不应强迫使用者依赖于它们不用的方法。

主要是记住 设计一个更小的接口,把最相关的接口(一组方法)放在一起,把另外相关的接口 放到另外的一个 接口中。 接口中的方法,对于接口的使用者 来说,应该都是需要的,并且必须需要的。 而不是 有一些 使用者需要接口的一部分方法,而另外的接口使用者需要全部的方法。

如果项目中出现这样的情况 就要思考一下,是不是接口设计的"太胖了" , 需要 重新把接口划分的更细。

设计接口的时候,有时候要多思考一下,接口使用者的感受。 是不是接口中的方法,对于接口的使用者来说 是否都需要,如果 是, 就没有问题。 如果不是,就要思考 接口的使用者 会必须使用哪些方法,把这些方法 做成一个接口,另外的部分做成另外的一个接口。

参考文档

敏捷软件开发:原则,模式,实践

极客时间-接口隔离原则

小话设计模式原则之:接口隔离原则ISP

分享快乐,留住感动. '2022-02-12 22:10:24' --frank
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值