【问题标题】:JAVA - extends vs interface - Strategy design patternJAVA - 扩展与接口 - 策略设计模式
【发布时间】:2015-05-19 10:10:52
【问题描述】:

我有一个场景,其中多个具体类extends 多个Abstract 类。我不知道想出一个干净的结构,减少文件的数量,避免代码重复。

要求是根据某些标准以不同的方式显示各种传感器值。温度、电压、电流等传感器值可以有一个模拟小部件、一个数字标签或两者的组合。我有 3 个Abstract 类,用于 3 种不同的视图。这 3 个Abstract 类实现了一个方法,该方法定义了如何绘制视图。每个传感器查看extends 3 个Abstract 类并实现读取传感器、执行一些工程转换和显示传感器值的方法。 这里的问题是,无论它扩展什么Abstract 类,具体类实现的代码都是相同的。 CAnalogTempViewCDigitalTempViewCCustomTempView 都有相同的实现,但扩展了不同的类。

这看起来很尴尬。代码重复,源文件的数量增加了 3 倍。我在这里遗漏了一些简单的东西吗?有这样的问题的模式吗?我可以在运行时extends 传感器视图类吗?实际代码更复杂。为了清楚起见,我已经过度简化了问题陈述。

编辑: 有几个传感器视图实现了Abstract 视图类。每个传感器的calculate() 方法是不同的。为简单起见,我刚刚列出了 3 个具体的类。同样,您将拥有CAnalogVoltageViewCDigitalVoltageViewCCustomVoltageViewCAnalogCurrentViewCDigitalCurrentViewCCustomCurrentView 等等

public abstract class CView
{
    public abstract void draw();
    public abstract void calculate();
}

public abstract class CAnalogView extends CView
{
    public void draw()
    {
         // draw specific to analog view
    }
}

public abstract class CDigitalView extends CView
{
    public void draw()
    {
        // draw specific to digital view
    }
}

public abstract class CCustomView extends CView
{
    public void draw()
    {
        // draw specific to custom view
    }
}

// concrete implementations
public class CAnalogTempView extends CAnalogView
{
    public void calculate()
    {
        // read and calculate sensor value here
    }
}

public class CDigitalTempView extends CDigitalView
{
    public void calculate()
    {
        // calculate here. same as CAnalogTempView::calculate()
    }
}

public class CCustomTempView extends CCustomView
{
    public void calculate()
    {
        // calculate here. same as CAnalogTempView::calculate()
    }
}

【问题讨论】:

  • 你可能过度继承了。这很容易完成。考虑使用委托。
  • 在考虑这样的抽象时,您需要做的是考虑“每个函数需要什么才能运行它的个人需求”。也就是说,如果您可以完成所有工作来获取calculate() 可以操作的几个值,那么您应该只获取这些值并传递它们。
  • 除了 CANalogTempView 之外,还有更多类扩展 CANalogView 吗?

标签: java multiple-inheritance code-reuse


【解决方案1】:

我会将Sensor 实现与View 分开。传感器不需要知道存在视图。我会创建一个Sensor 接口,其方法calculate() 返回一些结果(我在示例中使用double):

public interface Sensor {
    double calculate();
}

public class TemperatureSensor implements Sensor {
    public double calculate() {
        // specific implementation for the temperature sensor
    }
}

并将抽象的CView 更改为具有传感器的属性,以及简单地显示特定传感器计算结果的calculate() 的实现。

public abstract class CView {
    private Sensor sensor;

    CView(Sensor sensor) {
        this.sensor = sensor;
    }

    public abstract void draw();

    public void calculate() {
        double result = this.sensor.calculate();
        // display the result
    }
}

public class CDigitalView extends CView {
    CDigitalView(Sensor sensor) {
        super(sensor);
    }

    public void draw() {
        // draw specific to digital view
    }
}

所以每次你创建一个视图时,你都会像这样在构造函数中传递你想要显示的传感器:

Sensor temperatureSensor = new TemperatureSensor();
CView digitalView = new CDigitalView(temperatureSensor);

这样,每个传感器都有一个具体的类,每个视图都有一个具体的类。如果 n 是视图数,m 是传感器数,则总数为 n + m。通过模拟、数字和自定义视图,加上温度、电压和电流传感器,您将获得 6 个类。

相反,通过为每个视图的每个传感器创建一个具体类,正如您所做的那样,您将获得 n * m 个具体类。在上面的示例中,总数为 9,不包括 3 个抽象视图类。

【讨论】:

  • 每个传感器的calculate()方法不同。对于同一个传感器,calculate() 方法对于所有类型的视图都是相同的。
【解决方案2】:

我唯一能看到的是你可以在你的CView 类中提供public abstract void calculate() 的实现。如果任何类想要覆盖它就会覆盖它

【讨论】:

    【解决方案3】:

    策略设计模式会帮助你。嗯,你应该记住一件事。

    尽量使用lessextends关键字,最好使用Interfacescomposition

    解决方案:

    封装calculate(),因为calculate()将来可能会发生变化,它有不同的实现。

    流程:

    1) 创建一个具有calculate() 的接口CalcInterface

    2) 通过 3 个不同的类实现 CalcInterface,例如 CAnalogTempCalcCDigitalTempCalcCCustomTempCalc。然后在每个类中实现calculate()

    3) 现在是composition 的时间了。假设您有Sensor 类(主类)...然后创建CalcInterface 类型的对象。 PS:它将具有所有人通用的display()

    4) 现在创建 3 个不同的类,extends SensorAnalogSensorTempSensorCustomSensor ...

    5) 现在在运行时,您将创建任何类型的(CAnalogTempCalcCDigitalTempCalcCCustomTempCalc)对象。

    编辑:

    下面是类图,我不擅长艺术……但是这张图会让你对类和接口以及如何有效地使用它们有所了解。

    现在您只需实现 CalcInterface ... 即可实现任意数量的 CustomCalcuations

    这是POWER,可以通过遵循策略设计模式来适应变化而不改变当前的实现。

    【讨论】: