代码之家  ›  专栏  ›  技术社区  ›  Matthew Stopa

使用敏捷方法或其他方法编写失败耦合的代码的建议

  •  2
  • Matthew Stopa  · 技术社区  · 15 年前

    我最近一直在非常认真地阅读罗伯特·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 回复  |  直到 15 年前
        1
  •  1
  •   Graham    15 年前

    看看您的示例代码,您的图表类与ChartCalculation类紧密耦合(它正在实例化它的一个新实例)。如果chart类通过接口(我的示例代码中的ichartCalculation)使用chartCalculation,则可以移除此耦合。图表类使用的IChartCalculation的具体实现可以由其构造函数传递给图表类( dependency injection ),也许是 factory class . 另一种选择可能是使用 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
        }
    }
    
        2
  •  1
  •   Vinko Vrsalovic    15 年前

    在我看来,您对(相对)复杂的软件开发的耦合理解有误。

    如果要传递要使用的数据,无论是通过类还是通过多个类传递,使用者所能做的就是使用数据访问类的公共接口。对于耦合来说这是很好的,因为您可以更改这些类的内部实现方式,而不破坏任何东西,只要您有接口常量。数据可以有许多客户机,只要它们不依赖于指定的公共接口。

    如果您访问了类的私有成员,或者传递了数组的引用,则会出现耦合问题,其他类公开要由第三方修改,依此类推。

    尽管如此,如果你想改变这个公共接口,并且这个类在很多地方被使用,但是不管怎样,这都会发生,即使你使用组合而不是参数传递,所以最重要的是设计好你的公共接口,这样改变就不是一个常见的事情。

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

    编辑:首先,是否确实需要CartCalculations,或者您创建该类只是为了遵循一些规则?那么,如果需要进行cartcalculations,为什么要传递int[]而不是cartems,在这里可以控制要执行的操作以及如何对项目列表进行控制?最后,你为什么觉得那很脆弱?因为您可能忘记传递参数(编译错误,没有大问题)?因为有些人可能会在不应该修改的地方修改列表(通过只有Cartems才加载数据,这有点可控)?因为如果您需要更改项的表示方式(同样,如果您将数组包装在一个可以进行此类更改的类中,就没有什么大不了的了)。

    因此,假设所有这些层次结构都是有保证的,并将图表更改为购物车,因为它对我更有意义:

    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

        3
  •  1
  •   Kire Haglin    15 年前

    为了避免耦合,在您的示例中,我将创建某种数据提供程序,它可以将数据点提取到您的图形中。通过这种方式,您可以拥有几种不同类型的数据源,这些数据源的更改取决于您想要绘制的数据类型。例如。

    interface DataProvider
    {
      double[] getDataPoints(); 
    }
    
    class Chart 
    {
     void setDataProvider(DataProvider provider)
     {
       this.provider = provider;
     }
    }
    

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

        4
  •  0
  •   VonC    15 年前

    代码完成调用它” 流线参数传递 “:

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

        5
  •  0
  •   James Black    15 年前

    有些耦合在应用程序中是有用的,我认为是必要的,而且如果更改类型,您将得到编译器错误这一事实并不一定会使它变得脆弱。

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

    例如,如果您希望不总是使用 int[] ,但以后需要 ComplexDataType[] 然后您可能希望将这些数据放入它自己的类中,并将该类作为参数传递。这样,如果您需要进行更改,那么您希望将它从只映射整数数据扩展到复数,然后创建新的setter和getter,以便将数据与应用程序隔离开来,并且只要您遵守已经同意的合同,就可以随意更改实现,而不必太在意。所以,如果你说 int 数组可以在构造函数中使用,也可以用作setter,然后允许使用,但也允许使用其他类型,即 double , ComplexDataType 例如。

    要注意的一件事是,如果您希望类所做的转换影响到您的数据的所有实例。所以,您有数据传输对象,并且有两个线程。一个创建三维球面图,另一个创建二维饼图。我可能想修改每个图形中的DTO,使其成为一种图形格式,但是如果改变了球面坐标的数据,它会给饼图带来各种各样的问题。因此,在这一点上,当您传递数据时,您可能希望利用 final 关键字,并使您的数据不可变,因此您传递的任何内容都将是一个副本,但允许它们在显式请求时替换信息(它们调用setter)。

    我认为这将有助于减少您调用几个类时的问题,但是如果修改了DTO,您可能需要将其返回,但最终它可能是一个更安全的设计。

    推荐文章