建议用敏捷方法或其他方式编写松散耦合代码

时间:2009-11-24 06:39:43

标签: design-patterns oop agile decoupling

我最近一直非常强烈地阅读罗伯特C.马丁(又名叔叔鲍勃)的书。我发现他谈到的很多东西都是我的真正生命储蓄者(功能小,对事物的描述性很强等)。

我还没有遇到的一个问题是代码耦合。我一遍又一遍地遇到的一个问题是,我将创建一个对象,例如包装数组的东西。我会在一个类中对它进行一些工作,但是然后必须调用另一个类在另一个类中对它进行工作。到了我将相同的数据传递到3-4级深度,这似乎没有意义,因为很难保持这个对象通过的所有位置,所以当它需要更改它时我有很多依赖。这似乎不过是一种很好的做法。

我想知道是否有人知道更好的方法来解决这个问题,似乎鲍勃的建议(尽管可能是我误解了它)似乎让它变得更糟,因为它让我创造了更多的课程。提前谢谢。

编辑:通过请求一个真实世界的例子(是的,我完全同意很难理解其他):

class ChartProgram () {
int lastItems [];

  void main () {
  lastItems = getLast10ItemsSold();
  Chart myChart = new Chart(lastItems);
  }
}
class Chart () {
  int lastItems[];
  Chart(int lastItems[]) {
  this.lastItems = lastItems;
  ChartCalulations cc = new ChartCalculations(this.lastItems);
  this.lastItems = cc.getAverage();
}
class ChartCalculations {
  int lastItems[];
  ChartCalculations (int lastItems[]){
  this.lastItems = lastItems;
  // Okay so at this point I have had to forward this value 3 times and this is 
  // a simple example. It just seems to make the code very brittle
  }
  getAverage() {
  // do stuff here
  }

}

5 个答案:

答案 0 :(得分:1)

在我看来,你误解了与(相对)复杂的软件开发的耦合。

如果要传递要使用的数据,无论是通过类还是多个类,所有消费者都可以做的是使用数据访问类的公共接口。关于耦合,这很好,因为只要你的接口不变,你就可以改变那些内部实现的类,而不会破坏任何东西。只要数据不依赖于指定的公共接口,就可以拥有许多客户端。

如果您访问类的私有成员,或者如果您传递了数组的引用,则另一个类暴露给第三方修改,等等,您将遇到耦合问题。

虽然如果你想改变那个公共界面并且正在许多地方使用该类,但是无论如何都会发生这种情况,即使你使用了合成而不是参数传递,所以最重要的是设计你的公众接口足够好,所以变化不常见。

总结一下,虽然有时这可能指向一个设计问题(一个更好的设计可能转化为一个类层次结构,你不需要传递那么多的数据),但它本身并不坏,并且在某些情况下甚至需要它。

编辑:首先,CartCalculations是真的需要还是你创建该类只是为了遵循一些规则?那么,如果有保证CartCalculations,为什么你传递int []而不是CartItems,你可以控制什么做以及如何对项目列表?最后,为什么你觉得这很脆弱?因为你可能忘记传递参数(编译错误,没有大问题)?因为有人可能会修改它不应该的列表(通过让CartItems加载数据的唯一一个可控制)?因为如果您需要更改项目的表示方式(如果您将数组包装在可以进行此类更改的类中,则再次没有大问题。)

因此,假设所有这些层次结构都是有保证的,并将Chart更改为Cart,因为它对我来说更有意义:

class CartProgram () {
  CartItems lastItems;

  void main () {
    lastItems = getLast10ItemsSold();
    Cart myChart = new Cart(lastItems);
  }
}
class Cart () {
  CartItems lastItems;
  Cart(CartItems lastItems) {
  this.lastItems = lastItems;
  CartCalulations cc = new CartCalculations(this.lastItems);
  cc.getAverage();
}

class CartCalculations {
  CartItems lastItems;
  CartCalculations (CartItems lastItems){
  this.lastItems = lastItems;
  // Okay so at this point I have had to forward this value 3 times and this is 
  // a simple example. It just seems to make the code very brittle
  }
  getAverage() {
  // do stuff here
  }
}

class CartItems {
   private List<Item> itemList;

   public static CartItems Load(int cartId) {
       //return a new CartItems loaded from the backend for the cart id cartId
   }

   public void Add(Item item) {
   }

   public void Add(int item) {
   }

   public int SumPrices() {
       int sum = 0;
       foreach (Item i in itemList) {
          sum += i.getPrice()
       }
       return sum;
   } 
}

class Item 
{
    private int price;
    public int getPrice() { return price; }
}

对于架构良好的图表库,请参阅http://www.aditus.nu/jpgraph/jpgarchitecture.php

答案 1 :(得分:1)

为避免耦合,在您的示例中,我将创建某种可以将数据点提取到图形的DataProvider。这样,您可以根据想要绘制的数据类型,更改几种不同类型的数据源。 E.g。

interface DataProvider
{
  double[] getDataPoints(); 
}

class Chart 
{
 void setDataProvider(DataProvider provider)
 {
   this.provider = provider;
 }
}

然后我会完全跳过ChartCalculation类,只是在图表类中进行计算。如果您觉得需要重用代码来计算内容,我会创建一个工具包类(类似于Java中的Math类),它可以计算平均值,中值,总和或任何您需要的值。这个类是通用的,它对图表或最后项目的含义一无所知。

答案 2 :(得分:1)

查看示例代码,Chart类与ChartCalculation类紧密耦合(它实例化了一个新的实例)。如果Chart类通过接口使用ChartCalculation(我的示例代码中的IChartCalculation),则可以删除此耦合。 Chart类使用的IChartCalculation的具体实现可以通过它的构造函数(dependency injection)传递给Chart类,也许可以通过factory class传递给Chart类。另一种方法可能是使用IOC框架(尽管这通常会带来比必要的复杂性更多)。通过这种方式,Chart类可以使用IChartCalculation的不同具体实现,只要它们被编码为/实现接口,它们就可以独立变化。

class ChartProgram () 
{
    int lastItems [];

    void main () {
    lastItems = getLast10ItemsSold();
    Chart myChart = new Chart(lastItems, new ChartCalculations());
}

class Chart
{
    int[] lastItems;

    IChartCalculation chartCalculations;

    public Chart(int[] lastItems, IChartCalculations cc)
    {
        this.lastItems = lastItems;
        chartCalculations = cc;
        int average = cc.GetAverage(lastItems);
    }
}

interface IChartCalculation
{
    int GetAverage(int[] lastItems);
}


class ChartCalculation : IChartCalculation
{
    public int GetAverage(int[] lastItems)
    {
        // do stuff here
    }
}

答案 3 :(得分:0)

代码完成称之为“简化参数传递”:

  

如果您在多个例程中传递参数,则可能表示需要将这些例程分解为将参数作为对象数据共享的类。
  简化参数本身并不是目标,但是传递大量数据表明不同的类组织可能会更好地工作。

答案 4 :(得分:0)

某些耦合在应用程序中很有用,我认为有必要,如果你改变你的类型就会遇到编译器错误的事实并不一定会使它变脆。

我认为如何看待这一点取决于您将如何使用数据,以及类型可能会如何变化。

例如,如果您希望不会总是使用int[],但稍后需要ComplexDataType[],那么您可能希望将此数据放入其自己的类中,并将该类传递给一个参数。这样,如果您需要进行更改,您希望将其从仅将整数数据映射到复数数据,然后您将创建新的setter和getter,以便将数据与应用程序隔离,并且您可以自由地更改实现只要您遵守已经同意的合同,您就可以毫不在意。因此,如果您声明int数组可以在构造函数中使用,或者作为setter使用,那么允许它,但也允许其他类型,例如doubleComplexDataType。 / p>

要注意的一件事是,您是否希望类可以执行的转换会影响数据的所有实例。所以,你有你的数据传输对象,你有两个线程。一个将创建一个3D球形图,另一个将创建一个2D饼图。我可能想要修改每个图形中的DTO,使其成为图形格式,但如果数据更改为球面坐标,则会导致饼图出现各种问题。那么,在那时,当您传递数据时,您可能希望利用参数上的final关键字,并使您的DTO不可变,因此您传递的任何内容都将是副本,但允许它们如果明确请求,则替换信息(他们称之为设置者)。

我认为这会有助于在深度调用多个类时减少问题,但如果修改了DTO,则可能需要返回DTO,但最终它可能是一种更安全的设计。