SOLID и инверсия зависимостей

Содержание

Слайд 2

О пользе проектирования Изначально любая система обладает некоторой гибкостью (agility), другими

О пользе проектирования

Изначально любая система обладает некоторой гибкостью (agility), другими словами,

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

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

Слайд 3

S O L I D

S O L I D

Слайд 4

Принцип единственной обязанности Каждый интерфейс должен отвечать за что-то одно. Индикатор

Принцип единственной обязанности

Каждый интерфейс должен отвечать за что-то одно.
Индикатор ответственности

– повод для изменений. Если есть несколько поводов для изменения, интерфейс совмещает несколько обязанностей.

Пример: класс для составления и печати отчета – должен быть один класс для составления и другой класс для печати.
То же относится и к методам – каждый метод должен делать что-то одно.

Слайд 5

Принцип открытости-закрытости Модуль или класс должен быть закрыт для изменений и

Принцип открытости-закрытости

Модуль или класс должен быть закрыт для изменений и открыт

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

Другая формулировка основана на использовании абстракций. Класс А должен зависеть не от класса В, а от интерфейса IB, который реализуется классом B.
Изменить поведение А можно, дав новую реализацию интерфейса IB, при этом код класса А не изменится.

Бертран Мейер

Роберт Мартин

Слайд 6

class Picture { List figures; public void Print() { foreach (var

class Picture
{
List

figures;
public void Print()
{
foreach (var f

in figures) {
if (f is Rectangle)
Console.Write((Rectangle)f.Info());
if (f is Square)
Console.Write((Square)f.Info());
}
}

Пример

Классы Фигура, Окружность, Прямоугольник и контейнер Рисунок.
Это плохое проектирование.

class Rectangle: Figure
{
int w, h;
public Rectangle(int x, int y, int w, int h)
{
X = x; Y = y; this.w = w; this.h = h;
}
public int S { get { return w * h; } }
public string Info()
{
return string.Format("x={0} y={1} w={2} h={3} ",
X, Y, w, h);
}
}

class Figure
{
public int X {set; get;}
public int Y {set; get;}
}

class Square: Figure
{
int w;
public Square(int x, int y, int w)
{
X = x; Y = y; this.w = w;
}
public int S { get { return w * w; } }
public string Info()
{
return string.Format("x={0} y={1} w={2}", X, Y, w);
}
}

Слайд 7

abstract class Figure { public int X { set; get; }

abstract class Figure
{
public int X { set; get; }
public

int Y { set; get; }
public int S { get; }
public string Info();
}

class Rectangle : Figure
{
int w, h;
public Rectangle(int x, int y, int w, int h)
{
X = x; Y = y; this.w = w; this.h = h;
}
public override int S { get { return w * h; } }
public override string Info()
{
return string.Format("x={0} y={1} w={2} h={3} ",
X, Y, w, h);
}
}

class Square : Figure
{
int w;
public Square(int x, int y, int w)
{
X = x; Y = y; this.w = w;
}
public override int S { get { return w * w; } }
public override string Info()
{
return string.Format("x={0} y={1} w={2}", X, Y, w);
}
}

Пример (продолжение)

class Picture
{
List

figures;
public void PrintInfo()
{
foreach (var f in figures)
Console.WriteLine(f.Info());
}
public int S()
{
return figures.Sum(f => f.S);
}
}

Класс Picture зависит от абстракции. Изменение реализации конкретных фигур, даже появление новых фигур не меняют код Picture.

Слайд 8

Принцип подстановки Функции, которые используют базовый тип, должны иметь возможность использовать

Принцип подстановки

Функции, которые используют базовый тип, должны иметь возможность использовать подтипы

базового типа даже не зная об этом.

 Барбара Лисков

Определение подтипа: Тип S будет подтипом Т тогда и только тогда, когда соблюдает принцип подстановки.

Язык C++ не поддерживает принцип подстановки, а C# и Java поддерживают.
Вопрос. Если при реализации интерфейса IList метод Insert() будет добавлять не один, а два дубликата экземпляра, нарушится принцип подстановки?

Слайд 9

Контракт = Интерфейс Программирование по контракту + Предусловия + Постусловия +

Контракт = Интерфейс

Программирование по контракту

+ Предусловия
+ Постусловия
+ Инварианты

Предусловия –

это ограничения, которые накладываются на входные параметры и внешние переменные методов, например, функция Gcd(a, b), которая находит НОД, требует, чтобы a >= 0, b >= 0 и a <=b.
Постусловия – это ограничения, которые накладываются на возвращаемые значения методов, выходные параметры, и состояние внешних переменных после завершения метода. Например, если r - наибольший общий делитель, то r > 0 && r <=b.
Инварианты – это условия, которые относятся к классу в целом и должны выполняться на всем протяжении жизни экземпляра класса. Например, в классе List объем захваченной памяти больше или равен объему памяти, занятому данными, а в классе SortedList данные к тому же всегда упорядочены. Инвариант – это и пред- и постусловие одновременно.
Кроме того, инварианты, как часть контракта, наследуются производными классами.
Слайд 10

Контракты в .NET http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx Библиотека и статический класс Contract. Преобразователь кода

Контракты в .NET

http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx

Библиотека и статический класс Contract.
Преобразователь кода – ccrewrite.exe.
Анализатор кода

– cccheck.exe.

Первая часть это библиотека. Контракты кодируются с использованием вызова статических методов класса Contract (пространство имен System.Diagnostics.Contracts) из сборки mscorlib.dll. Контракты имеют декларативный характер, и эти статические вызовы в начале тела метода можно рассматривать как часть сигнатуры метода. Они являются методами, а не атрибутами, поскольку атрибуты очень ограничены, но эти концепции близки.
Вторая часть это binary rewriter, ccrewrite.exe. Этот инструмент модифицирует инструкции MSIL и проверяет контракт. Это инструмент дает возможность проверки выполнения контрактов, чтобы помочь при отладке кода. Без него, контракты просто документация и не включается в скомпилированный код.
Третья часть это инструмент статической проверки, cccheck.exe, который анализирует код без его выполнения и пытается доказать, что все контракты выполнены.

Слайд 11

Code Contracts в Студии

Code Contracts в Студии

Слайд 12

// Прямоугольник с фиксированным периметром и шириной больше высоты. class MyRect

// Прямоугольник с фиксированным периметром и шириной больше высоты.
class

MyRect
{
double p, w, h;
[ContractInvariantMethod]
void ObjectInvariant()
{
Contract.Invariant(p == w + h);
Contract.Invariant(w >= h);
Contract.Invariant(h > 0);
}
public MyRect(double w, double h)
{
this.w = w;
this.h = h;
p = w + h;
}
public double H
{
// Изменение высоты не должно превышать 10 единиц за раз.
set {
// Проверка в форме предусловия.
Contract.Requires(Math.Abs(value - H) < 10.0, "Too large change.");
h = value;
w = p - h;
}
get { return h; }
}
public double W
{
// Изменение ширины не должно превышать 10 единиц за раз.
set
{
// Проверка в форме постусловия.
Contract.Ensures(Math.Abs(Contract.OldValue(w) - w) < 10.0, "Too large...");
w = value;
h = p - w;
}
get { return w; }
}
public double Square()
{
// Площадь всегда положительна.
Contract.Ensures(Contract.Result() > 0, "Squere must be positive");
return w * h;
}
}

Пример

Слайд 13

Принцип разделения интерфейсов Принцип разделения интерфейсов говорит о том, что слишком

Принцип разделения интерфейсов

Принцип разделения интерфейсов говорит о том, что слишком «толстые»

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

Клиенты не должны зависеть от методов, которые они не используют.

Слайд 14

Принцип инверсии зависимостей Зависимости внутри системы строятся на основе абстракций (т.е.

Принцип инверсии зависимостей

Зависимости внутри системы строятся на основе абстракций (т.е. интерфейсов).


Модули верхнего уровня не зависят от модулей нижнего уровня.
Слайд 15

Пример зависимости // Воин владеет оружием public class Warrior { readonly

Пример зависимости

// Воин владеет оружием
public class Warrior
{
readonly Sword weapon;
//

Оружие получает при рождении
public Warrior(Sword weapon)
{
this.weapon = weapon;
}
// При помощи оружия может и убить
public void Kill()
{
weapon.Kill();
}
}

// Меч способен убивать
public class Sword
{
public void Kill()
{
Console.WriteLine("Chuk-chuck");
}
}

Warrior

Sword

class Program
{
static void Main()
{
Warrior warrior = new Warrior(new Sword());
warrior.Kill();
}
}

Есть два класса – Воин и Меч. Воин владеет мечем, а значит, зависит от него.

Слайд 16

Инверсия зависимости IWeapon Warrior Sword x // Оружие способно убивать public

Инверсия зависимости

IWeapon

Warrior

Sword

x

// Оружие способно убивать
public interface IWeapon
{

void Kill();
}

// Базука – оружие, поэтому способна убивать
public class Bazuka : IWeapon
{
public void Kill()
{
Console.WriteLine("BIG BADABUM!");
}
}

// Меч – оружие, поэтому способен убивать
public class Sword : IWeapon
{
public void Kill()
{
Console.WriteLine("Chuk-chuck");
}
}

Воин зависит от абстракции.
И меч зависит от абстракции.
От меча воин не зависит.

Слайд 17

Код после инверсии зависимости public class Warrior { readonly IWeapon weapon;

Код после инверсии зависимости

public class Warrior
{
readonly IWeapon weapon;

public Warrior(IWeapon weapon)
{
this.weapon = weapon;
}
public void Kill()
{
weapon.Kill();
}
}

class Program
{
static void Main()
{
Warrior warrior = new Warrior(new Sword());
warrior.Kill();
}
}

public interface IWeapon
{
void Kill();
}

public class Sword : IWeapon
{
public void Kill()
{
Console.WriteLine("Chuk-chuck");
}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IoC
{
public interface IWeapon
{
void Kill();
}
public class Warrior
{
readonly IWeapon weapon;
public Warrior(IWeapon weapon)
{
this.weapon = weapon;
}
public void Kill()
{
weapon.Kill();
}
}
public class Sword : IWeapon
{
public void Kill()
{
Console.WriteLine("Chuk-chuck");
}
}
class Program
{
static void Main()
{
Warrior warrior = new Warrior(new Sword());
warrior.Kill();
}
}
}

Слайд 18

IoC-контейнер IoC контейнер - это служба для управления созданием объектов. Составные

IoC-контейнер

IoC контейнер - это служба для управления созданием объектов.
Составные части контейнера:
Регистратор

реализаций
Фабрика объектов
Alpha --> IAlpha
Beta --> IBeta
Sword --> IWeapon
Фабрика объектов

IWeapon

Sword

Слайд 19

Пакет Ninject и его установка Меню: TOOLS / Library Package Manager

Пакет Ninject и его установка

Меню: TOOLS / Library Package Manager /

Package Manager Console
или
Команда в PM-консоли: PM> Install-Package Ninject
Слайд 20

Ninject в настольном приложении using Ninject.Modules; public class WeaponNinjectModule : NinjectModule

Ninject в настольном приложении

using Ninject.Modules;
public class WeaponNinjectModule : NinjectModule

{
public override void Load() {
// Регистрация реализации
this.Bind().To();
}
}

using Ninject;
class Program
{
public static IKernel AppKernel;
static void Main()
{
// Фабрика объектов
AppKernel = new StandardKernel(new WeaponNinjectModule());
var warrior = AppKernel.Get();
}
}

Слайд 21

Атрибут [Inject] public class Warrior { [Inject] public IWeapon Weapon {

Атрибут [Inject]

public class Warrior
{
[Inject]
public IWeapon Weapon {

set; get; }
public void Kill()
{
Weapon.Kill();
}
}

public class Warrior
{
readonly IWeapon weapon;
public Warrior(IWeapon weapon)
{
this.weapon = weapon;
}
public void Kill()
{
weapon.Kill();
}
}

Ninject инициализирует автоматические свойства, если они открытые и помечены атрибутом [Inject]

Версия класса Warrior со свойством Weapon

Версия класса Warrior с полем weapon