外观模式还是装饰器模式?

5

背景:

我有一个REST服务,假设叫做CustomerService,它目前只有一个方法getCustomer(id, country)。现在的要求是,根据国家不同,我需要执行不同的业务逻辑,比如访问不同的数据库或者一些自定义规则,并且报告我收到了这样一个请求。

首先,为了解决根据国家不同而产生的不同实现,我使用了工厂模式,如下所示:

所有基于国家的实现的公共接口

public Interface CustomerServiceHandler{
   Cusomer getCustomer(String id, String country);
}

然后工厂作为
public class CustomerServiceHandlerFactory{
   public CustomerServiceHandler getHandler(String country){...};
}

使用Facade的实现细节

请注意,此Facade从REST类CustomerService调用。

public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        //use factory to get handler and then handler.getCustomer
        Customer customer = factory.getHandler(country).getCustomer(id,country);
        //report the request
        reportingService.report('fetch-customer',....);
        return customer;
   }
}

根据单一职责原则(SRP),这个门面模式没有实现单一目标。它既获取客户端信息,又报告该请求已经接收。因此,我考虑使用以下装饰器模式进行实现。

使用装饰者模式的实现方法:

//this is called from Rest layer
public ReportingCustomerHandler implements CustomerServiceHandler{
    //this delegate is basically the default implementation and has factory too                    
    private CustomerServiceHandler delegate;
    private ReportingService reporting;
    public Customer getCustomer(String id, String country){
           Customer customer = delegate.getCustomer(id, country);
           reporting.report(....);
           return customer;
    }
}


 //this is called from ReportingCustomerHandler           
 public DefaultCustomerServiceHandler implements CustomerServiceHandler{
     private CustomerServiceHandlerFactory factory;                    

     public Customer getCustomer(String id, String country){
         //get factory object else use itself, even default implementation is provided by factory
         CustomerServiceHandler handler = factory.getHandler(country);
         return handler.getCustomer(id,country);

     }
 }

注意:在第二种方法中,我重用了工厂代码中展示的CustomerServiceHandler接口,同时用于报告和默认实现

那么正确的方式是什么,或者如果有更合适的方法,该怎么办。

问题的第二部分

如果我必须维护两个不同的接口,即一个用于实现不同国家的实现,另一个用于服务REST层。那么设计或替代方案是什么?在这种情况下,我认为门面模式很适合。

1个回答

1
所以,什么是正确的方法,或者说有什么替代方法?
您的设计很好,并且巧妙地使用了工厂模式。我对这个好工作提出建议,但我认为有很多方法可以增强您所拥有的东西。
我可以看到CustomerServiceFacade方法getCustomer正在打破SRP。 它将检索客户与报告方面结合在一起。 我同意将报告移出该方法会更清晰。
然后您的对象将如下所示:
public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        return factory.getHandler(country).getCustomer(id,country);
   }
}

那么我们应该把报告放在哪里呢?

你可以通过单独的界面移动/管理报告。这将允许实现不同的报告方法并使测试更容易(例如模拟报告部分)。

public interface ReportService {
   void report(Customer c, String id, String country);
}

REST层如何访问报告?

选项1:通过多个对象,REST访问各种客户功能

可以使用CustomerServiceFacadeReportService的实现注入到REST控制器中。

不确定您正在使用哪个REST框架,但以下是它可能看起来像的样子:

@GET
@Path("/customer/{Id}/{country}")
public Response getCustomer(@PathParam("Id") String id, @PathParam("country") String country){

    Response r = null;

    // injected implementation of CustomerServiceFacade
    Customer c = customerServiceFacade.getCustomer(id, country);
    if (c!=null){
        // injected implementation of ReportService
        reportService.report(c, id, country);
    }
    else {
        // handle errors ...
    }

    return r;
}

选项2:通过一个Facade/Service访问各种客户功能的REST

您可以允许服务外观层提供简化接口以访问提供能力的较大对象集。这可以通过拥有多个客户服务方法来实现,使得REST层可以通过一个对象访问各种能力,但仍然具有每个方法更紧密地遵循SRP的好处。

在这里,我们将CustomerServiceFacade注入到REST Controller中,并调用两个方法:1)获取客户和2)处理报告。该facade使用上述ReportService接口的实现。

public CustomerServiceFacade{
   public Customer getCustomer(String id, String country){
        // call the correct CustomerServiceHandler (based on country)
        return factory.getHandler(country).getCustomer(id,country);
   }

   public void report(Customer c, String id, String country){
        // call the reporting service 
        reportService.report(c, id, country);
   }
}

我认为这是使用Facade模式的合理用法,同时在实际方法中仍然具有SRP。
如果报告实现方式与客户端一样因国家而异,您可以使用另一个工厂。
   public void report(Customer c, String id, String country){
        // call the correct reporting service (based on country)
        rptFactory.getInstance(country).report(c,id,country);
   }

谢谢您的建议。关于报告,我不想将其移动到 REST 层,因为我喜欢保持 REST 层独立于任何逻辑,以便轻松切换该接口与任何实现。另外,您觉得装饰器模式怎么样? - Sikorski
装饰器可以使用,但是您的 ReportingCustomerHandler 调用了 getCustomerreport,这与我们在 CustomerServiceFacade 中所遇到的“问题”相似,因此我尽可能地设法避免这种情况。 - Mike Barlotta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接