Что такое "слабая связь"? Пожалуйста, приведите примеры - PullRequest
164 голосов
/ 22 октября 2008

Кажется, я не могу понять, что такое «слабая связь». Я полагаю, это не помогает тому, что слово «свободный» обычно имеет отрицательный оттенок, поэтому я всегда забываю, что слабая связь - это вещь хорошая .

Кто-нибудь покажет какой-нибудь код "до" и "после" (или псевдокод), который иллюстрирует эту концепцию?

Ответы [ 20 ]

163 голосов
/ 23 октября 2008

Рассмотрим простое приложение для корзины покупок, которое использует класс CartContents для отслеживания товаров в корзине и класс Order для обработки покупки. Заказ должен определить общую стоимость содержимого в корзине, он может сделать это так:

Пример плотной связи:

public class CartEntry
{
    public float Price;
    public int Quantity;
}

public class CartContents
{
    public CartEntry[] items;
}

public class Order
{
    private CartContents cart;
    private float salesTax;

    public Order(CartContents cart, float salesTax)
    {
        this.cart = cart;
        this.salesTax = salesTax;
    }

    public float OrderTotal()
    {
        float cartTotal = 0;
        for (int i = 0; i < cart.items.Length; i++)
        {
            cartTotal += cart.items[i].Price * cart.items[i].Quantity;
        }
        cartTotal += cartTotal*salesTax;
        return cartTotal;
    }
}

Обратите внимание, как метод OrderTotal (и, следовательно, класс Order) зависит от деталей реализации классов CartContents и CartEntry. Если бы мы попытались изменить эту логику, чтобы учесть скидки, нам, вероятно, пришлось бы изменить все 3 класса. Кроме того, если мы перейдем к использованию коллекции List для отслеживания элементов, нам также придется изменить класс Order.

Теперь вот немного лучший способ сделать то же самое:

Пример без пары:

public class CartEntry
{
    public float Price;
    public int Quantity;

    public float GetLineItemTotal()
    {
        return Price * Quantity;
    }
}

public class CartContents
{
    public CartEntry[] items;

    public float GetCartItemsTotal()
    {
        float cartTotal = 0;
        foreach (CartEntry item in items)
        {
            cartTotal += item.GetLineItemTotal();
        }
        return cartTotal;
    }
}

public class Order
{
    private CartContents cart;
    private float salesTax;

    public Order(CartContents cart, float salesTax)
    {
        this.cart = cart;
        this.salesTax = salesTax;
    }

    public float OrderTotal()
    {
        return cart.GetCartItemsTotal() * (1.0f + salesTax);
    }
}

Логика, характерная для реализации позиции корзины, коллекции корзины или заказа, ограничена только этим классом. Таким образом, мы можем изменить реализацию любого из этих классов без необходимости изменения других классов. Мы могли бы пойти еще дальше, улучшив дизайн, представив интерфейсы и т. Д., Но я думаю, вы понимаете, в чем дело.

150 голосов
/ 31 декабря 2008

Многие интегрированные продукты (особенно Apple), такие как iPod , iPad , являются хорошим примером тесной связи: когда батарея разрядится, вы также можете купить новое устройство, потому что Аккумуляторная батарея припаяна и не разрыхляется, что делает замену очень дорогой. Слабосвязанный плеер позволит легко менять батарею.

То же самое относится к разработке программного обеспечения: как правило (намного) лучше иметь слабосвязанный код для облегчения расширения и замены (и для облегчения понимания отдельных частей). Но очень редко при особых обстоятельствах может быть выгодно тесное соединение, поскольку тесная интеграция нескольких модулей обеспечивает лучшую оптимизацию.

51 голосов
/ 22 октября 2008

Я буду использовать Java в качестве примера. Допустим, у нас есть класс, который выглядит так:

public class ABC
{
   public void doDiskAccess() {...}
}

Когда я позвоню в класс, мне нужно будет сделать что-то вроде этого:

ABC abc = new ABC();

abc. doDiskAccess();

Пока все хорошо. Теперь допустим, что у меня есть другой класс, который выглядит так:

public class XYZ
{
   public void doNetworkAccess() {...}
}

Он выглядит точно так же, как ABC, но, скажем, он работает по сети, а не на диске. Итак, теперь давайте напишем такую ​​программу:

if(config.isNetwork()) new XYZ().doNetworkAccess();
else new ABC().doDiskAccess();

Это работает, но немного громоздко. Я мог бы упростить это с помощью интерфейса, подобного этому:

public interface Runnable
{
    public void run();
}

public class ABC implements Runnable
{
   public void run() {...}
}

public class XYZ implements Runnable
{
   public void run() {...}
}

Теперь мой код может выглядеть так:

Runnable obj = config.isNetwork() ? new XYZ() : new ABC();

obj.run();

Видите, насколько чище и проще это понять? Мы только что поняли первый основной принцип слабой связи: абстракция. Ключевым моментом здесь является обеспечение того, чтобы ABC и XYZ не зависели от каких-либо методов или переменных классов, которые их вызывают. Это позволяет ABC и XYZ быть полностью независимыми API. Или, другими словами, они «отделены» или «слабо связаны» с родительскими классами.

Но что, если нам понадобится общение между ними? Что ж, тогда мы можем использовать дополнительные абстракции, такие как Event Model , чтобы гарантировать, что родительский код никогда не будет связан с созданными вами API.

36 голосов
/ 22 октября 2008

Извините, но "слабая связь" - это не проблема кодирования, это проблема дизайна. Термин «слабая связь» тесно связан с желательным состоянием «высокой когезии», являясь противоположным, но дополняющим.

Слабая связь просто означает, что отдельные элементы дизайна должны быть сконструированы так, чтобы количество ненужной информации, которую они должны знать о других элементах дизайна, было уменьшено.

Высокая когезия является своего рода «жесткой связью», но высокая когезия - это состояние, когда элементы дизайна, которые действительно должны знать друг о друге, спроектированы так, чтобы они работали вместе чисто и элегантно.

Дело в том, что некоторые элементы дизайна должны знать подробности о других элементах дизайна, поэтому они должны быть спроектированы таким образом, а не случайно. Другие элементы дизайна не должны знать подробности о других элементах дизайна, поэтому они должны разрабатываться таким образом, целенаправленно, а не случайно.

Выполнение этого оставлено в качестве упражнения для читателя :).

33 голосов
/ 22 октября 2008

Тесно связанный код опирается на конкретную реализацию. Если мне нужен список строк в моем коде, и я объявляю его следующим образом (в Java)

ArrayList<String> myList = new ArrayList<String>();

тогда я зависим от реализации ArrayList.

Если я хочу изменить это на слабосвязанный код, я делаю ссылку на интерфейсный (или другой абстрактный) тип.

List<String> myList = new ArrayList<String>();

Это не позволяет мне вызывать любой метод на myList, который специфичен для реализации ArrayList. Я ограничен только теми методами, которые определены в интерфейсе List. Если позже я решу, что мне действительно нужен LinkedList, мне нужно изменить свой код только в одном месте, где я создал новый список, а не в 100 местах, где я делал вызовы методов ArrayList.

Конечно, вы можете создать экземпляр ArrayList с помощью первого объявления и не использовать никакие методы, не являющиеся частью интерфейса List, но использование второго объявления заставляет компилятор сохранять честность .

24 голосов
/ 23 октября 2008

Степень разницы между ответами здесь показывает, почему было бы трудно понять концепцию, но изложить ее так просто, как я могу ее описать:

Для того, чтобы я знал, что если я брошу тебе мяч, тогда ты сможешь поймать его, мне действительно не нужно знать, сколько тебе лет. Мне не нужно знать, что вы ели на завтрак, и мне действительно все равно, кто был вашей первой страстью. Все, что мне нужно знать, это то, что вы можете поймать. Если я знаю это, то мне все равно, если это ты, я бросаю мяч тебе или твоему брату.

С помощью нединамических языков, таких как c #, Java и т. Д., Мы осуществляем это через интерфейсы. Допустим, у нас есть следующий интерфейс:

public ICatcher
{
   public void Catch();
}

А теперь допустим, что у нас есть следующие классы:

public CatcherA : ICatcher
{
   public void Catch()
   {
      console.writeline("You Caught it");
   }

}
public CatcherB : ICatcher
{
   public void Catch()
   {
      console.writeline("Your brother Caught it");
   }

}

Теперь и CatcherA, и CatcherB реализуют метод Catch, поэтому служба, для которой требуется Catcher, может использовать любой из них и на самом деле не беспокоиться о том, какой это. Таким образом, тесно связанная служба может напрямую привести к перехвату, т. Е.

public CatchService
{
   private CatcherA catcher = new CatcherA();

   public void CatchService()
   {
      catcher.Catch();
   }

}

Таким образом, CatchService может делать именно то, что он намеревался сделать, но он использует CatcherA и всегда будет использовать CatcherA. Он жестко закодирован, поэтому он остается там, пока кто-то не придет и не проведет рефакторинг.

Теперь давайте возьмем другой параметр, называемый внедрением зависимости:

public CatchService
{
   private ICatcher catcher;

   public void CatchService(ICatcher catcher)
   {
      this.catcher = catcher;
      catcher.Catch();
   }
}

Таким образом, вызов, который создает CatchService, может делать следующее:

CatchService catchService = new CatchService(new CatcherA());

или

CatchService catchService = new CatchService(new CatcherB());

Это означает, что служба Catch не тесно связана ни с CatcherA, ни с CatcherB.

Существует несколько других стратегий для служб слабого связывания, таких как использование инфраструктуры IoC и т. Д.

23 голосов
/ 22 октября 2008

Вы можете думать о (жесткой или слабой) связи как о буквальном количестве усилий, которые потребуются вам, чтобы отделить определенный класс от его зависимости от другого класса. Например, если у каждого метода в вашем классе есть небольшой блок finally, внизу которого вы вызывали Log4Net для записи чего-либо, то вы бы сказали, что ваш класс тесно связан с Log4Net. Если бы вместо этого ваш класс содержал закрытый метод с именем LogSomething, который был единственным местом, которое вызывало компонент Log4Net (а все остальные вместо этого все методы назывались LogSomething), то вы бы сказали, что ваш класс слабо связан с Log4Net (потому что это не займет много попытаться вытянуть Log4Net и заменить его чем-то другим).

12 голосов
/ 22 октября 2008

Определение

По сути, связь - это то, насколько данный объект или набор объектов полагается на другой объект или другой набор объектов для выполнения своей задачи.

Высокая связь

Подумай об автомобиле. Для запуска двигателя ключ должен быть вставлен в зажигание, повернут, должен присутствовать бензин, должна возникнуть искра, должны сработать поршни, и двигатель должен ожить. Можно сказать, что двигатель автомобиля сильно связан с несколькими другими объектами. Это высокая связь, но это не так уж плохо.

Слабая муфта

Подумайте о пользовательском элементе управления для веб-страницы, который отвечает за предоставление пользователям возможности публиковать, редактировать и просматривать некоторые типы информации. Единый элемент управления может использоваться, чтобы позволить пользователю опубликовать новый фрагмент информации или отредактировать новый фрагмент информации. Элемент управления должен иметь возможность совместного использования двух разных путей - новый и редактировать. Если элемент управления написан таким образом, что ему нужны данные какого-либо типа со страниц, на которых он будет содержаться, вы можете сказать, что он слишком сильно связан. Элемент управления не должен ничего делать со страницы своего контейнера.

7 голосов
/ 22 октября 2008

Это довольно общая концепция, поэтому примеры кода не дают полной картины.

Один парень здесь, на работе, сказал мне: «Шаблоны похожи на фракталы, вы можете увидеть их, когда вы приблизитесь очень близко, и когда вы уменьшите масштаб до уровня архитектуры».

Чтение краткой страницы Википедии может дать вам ощущение этой общности:

http://en.wikipedia.org/wiki/Loose_coupling

Что касается конкретного примера кода ...

Вот одна слабая связь, с которой я недавно работал, из материала Microsoft.Practices.CompositeUI.

    [ServiceDependency]
    public ICustomizableGridService CustomizableGridService
    {
        protected get { return _customizableGridService; }
        set { _customizableGridService = value; }
    }

Этот код объявляет, что этот класс зависит от CustomizableGridService. Вместо того, чтобы просто ссылаться на точную реализацию службы, он просто заявляет, что требует НЕКОТОРОЙ реализации этой службы. Затем во время выполнения система разрешает эту зависимость.

Если это неясно, вы можете прочитать более подробное объяснение здесь:

http://en.wikipedia.org/wiki/Dependency_injection

Представьте, что ABCCustomizableGridService - это имплементация, которую я собираюсь здесь подключить.

Если я решу это сделать, я смогу вытащить его и заменить на XYZCustomizableGridService или StubCustomizableGridService без каких-либо изменений в классе с этой зависимостью.

Если бы я непосредственно ссылался на ABCCustomizableGridService, то мне нужно было бы внести изменения в эти / те ссылки / ссылки, чтобы перейти в другую реализацию сервиса.

6 голосов
/ 23 октября 2008

Связывание связано с зависимостями между системами, которые могут быть модулями кода (функциями, файлами или классами), инструментами в конвейере, процессами сервер-клиент и т. Д. Чем менее общие зависимости, тем более «тесно они связаны» они становятся, поскольку для изменения одной системы требуется изменение других систем, которые на нее полагаются. Идеальная ситуация - это «слабая связь», когда одна система может быть изменена, и системы, зависящие от нее, продолжат работать без изменений.

Общий способ достижения слабой связи - через четко определенные интерфейсы. Если взаимодействие между двумя системами четко определено и соблюдается с обеих сторон, тогда становится легче модифицировать одну систему, гарантируя, что соглашения не нарушаются. На практике часто бывает, что четко определенный интерфейс не установлен, что приводит к небрежной конструкции и жесткой связи.

Некоторые примеры:

  • Приложение зависит от библиотеки. При жесткой связи приложение работает на более новых версиях библиотеки. Google для "DLL Hell".

  • Клиентское приложение считывает данные с сервера. В условиях жесткой связи изменения на сервере требуют исправлений на стороне клиента.

  • Два класса взаимодействуют в объектно-ориентированной иерархии. При тесной связи изменения одного класса требуют обновления другого класса для соответствия.

  • Несколько инструментов командной строки взаимодействуют в канале. Если они тесно связаны, изменения в версии одного инструмента командной строки вызовут ошибки в инструментах, которые читают его вывод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...