如何添加改变现有代码的Java实现功能?

时间:2018-02-18 05:47:45

标签: java oop design-patterns solid-principles

假设我已实施资金转账。现在我想添加认证功能,这应该在资金转移之前完成(考虑到我们已经在现有请求中接收用户名和密码)。我们应该使用哪种模式以及如何在不修改调用类和现有实现的情况下实现这一目标?

此时我能想到的是在扩展实现类之后使用decorator模式,但我相信我们仍然需要修改调用类。

请查找现有的界面和类。

package sb.test.demo.fundtransfer;

public interface FundTransferService {
    public boolean makeTransfer(TransferRequest request) throws Exception;
}

package sb.test.demo.fundtransfer;

public class FundTransferServiceImpl implements FundTransferService {

    @Override
    public boolean makeTransfer(TransferRequest request) throws Exception {
        //Dummy Code
        System.out.println("TransferDone");
        return true;
    }

}

package sb.test.demo.fundtransfer;

public class TestTransfer {

    public static void main(String[] args) throws Exception {
        TransferRequest request = new TransferRequest();
        request.setSourceAccount(123456);
        request.setDestinationAccount(654321);
        request.setTranserAmount(1000);
        request.setUserName("user1");
        request.setPassword("pass1");

        FundTransferService fts = new FundTransferServiceImpl();
        fts.makeTransfer(request);
    }

}

现在,我想要扩展FundTransferServiceImpl来createFundTransferServiceNEWImpl,它将添加身份验证。

package sb.test.demo.fundtransfer;

public class FundTransferServiceNEWImpl extends FundTransferServiceImpl {
    @Override
    public boolean makeTransfer(TransferRequest request) throws Exception {
        //Dummy Code
        System.out.println("Authenticating..");
        super.makeTransfer(request);
        System.out.println("TransferDone from NEW..");
        return true;
    }
}

现在,在不更改TestTransfer.java和FundTransferServiceImpl.java的情况下,如何调用FundTransferServiceNEWImpl的makeTransfer来添加身份验证?或者,还有其他方法可以实现相同的目标吗?

请有人帮我这个吗?

提前致谢!

3 个答案:

答案 0 :(得分:0)

你可以制作" FundTransferServiceNEWImpl"还实现了界面" FundTransferService"并提供您希望的实现,如果这是您要求的!!

答案 1 :(得分:0)

  

现在,在不更改TestTransfer.java和FundTransferServiceImpl.java的情况下,如何调用FundTransferServiceNEWImpl的makeTransfer来添加身份验证?

你不能不改变TestTransfer(来电者)或FundTransferServiceImpl(被叫者)的字节码。

有两种方法可以更改字节码。

你可以

  • 编辑源文件并编译
  • 在加载类之前编辑字节码

编辑源文件

我建议编辑TestTransfer课程。有问题的一行是

FundTransferService fts = new FundTransferServiceImpl();

因为这一行引入了从TestTransferFundTransferServiceImpl的依赖关系。

我还建议通过合成而不是继承来实现装饰器。例如。

public class AuthenticationFundTransferServiceWrapper implements FundTransferService {

    private FundTransferService fundTransferService;

    public AuthenticationFundTransferServiceWrapper(FundTransferService fundTransferService){
        this.fundTransferService = fundTransferService;
    }

    public boolean makeTransfer(TransferRequest request) throws Exception {
        //Dummy Code
        System.out.println("Authenticating..");
        fundTransferService.makeTransfer(request);
        System.out.println("TransferDone from NEW..");
        return true;
    }
}

优点是AuthenticationFundTransferServiceWrapper仅依赖于接口FundTransferService而不依赖于实现。这减少了依赖性并使课程更加灵活。

编辑字节代码

可以在加载类之前编辑字节码。

看看

答案 2 :(得分:0)

所以你已经识别出装饰器模式和this answer正确实现了装饰器,但由于这是一个SOLID原则问题,我将指出该选项中的缺陷。

要查看继承或装饰器中的缺陷,请考虑授权失败时会发生什么。如果它抛出一个新的异常类型,那就是 Liskov替换原则违规。如果它通过静默不转移资金来改变行为,那也是LSP违规。如果您要依赖返回的布尔值,则不会将有用的失败消息返回给用户或系统管理员。

正如我所看到的,客户端代码无法避免知道新实现正在检查授权,因为它需要处理新的异常或不同的返回值。

鉴于此,我建议您添加一个新类,如下所示:

public final class TransactionAuthorizationService {

    private final FundTransferService fundTransferService;

    public AuthenticationFundTransferServiceWrapper(FundTransferService fundTransferService){
        this.fundTransferService = fundTransferService;
    }

    public boolean authorizeAndMakeAndTransfer(TransferRequest request) throws Exception {
        //Dummy Code
        System.out.println("Authenticating..");
        fundTransferService.makeTransfer(request);
        System.out.println("TransferDone from NEW..");
        return true;
    }
}

优点:

  • 在客户端代码处理接口FundTransferService之前,你不知道在运行时他们有什么实现以及他们是否会授权事务,现在客户端代码现在处理TransactionAuthorizationService和他们致电authorizeAndMakeAndTransfer,这一点非常清楚。
  • 由于我们的新类没有实现现有接口,因此没有Liskov Substitution Violation,可以自由返回不同的值或抛出不同的异常。

其他提示:

  • 使用throw alls停止装饰方法:throws Exception
  • 不要使用InterfaceImpl作为类名,寻找使它们在抽象接口上具体化的内容。