Кирилл Корняков (Itseez, ННГУ)
Октябрь 2015
Зависимости — корень многих проблем!
float
для представления денег при переходе с рублей на долларыif/else
вместо полиморфизмаПрограммные сущности должны иметь только одну ответственность.
Не должно быть больше одной причины для изменения класса.
"Программные сущности должны быть открыты для расширения,
но закрыты для модификации."
Б. Мейер, 1988 / Р. Мартин, 1996
Нет ли здесь противоречия? Как этого можно добиться?
ReportFormat format = TXT;
Reporter reporter(format);
...
reporter.getAnnualReport();
class Reporter {
private TxtPrinter txtPrinter;
private XmlPrinter xmlPrinter;
Reporter(ReportFormat format) {
if (format == TXT)
txtPrinter = new TxtPrinter();
else if (format == XML)
xmlPrinter = new XmlPrinter();
}
void getAnnualReport() {
AnnualReport report = new AnnualReport();
if (txtPrinter)
txtPrinter.print(report);
else if (xmlPrinter)
xmlPrinter.print(report);
}
}
IPrinter printer = new TxtPrinter();
Reporter reporter(printer);
...
reporter.getAnnualReport();
class Reporter {
IPrinter printer;
Reporter(IPrinter printer) {
this.printer = printer;
}
void getAnnualReport() {
AnnualReport report = new AnnualReport();
printer.print(report);
}
}
"Ни одна значительная программа не может быть на 100% закрытой"
Р. Мартин, 1996
Абстракции не должны зависеть от деталей.
Детали должны зависеть от абстракций.
Procedural Architecture
Object-Oriented Architecture
enum OutputDevice { PRINTER, DISK };
void Copy(OutputDevice device) {
int c;
while ((c = ReadKeyboard()) != EOF)
if (device == PRINTER)
WritePrinter(c);
else
WriteDisk(c);
}
void Copy(Writer writer) {
int c;
while ((c = ReadKeyboard()) != EOF) {
writer.Write(c);
}
}
public interface IReader {
char Read();
}
public interface IWriter {
void Write(char c);
}
void Copy(IReader reader, IWriter writer) {
int c;
while ((c = reader.Read()) != EOF) {
writer.Write(c);
}
}
В чем преимущества такого подхода?
Wikipedia
public class MyApplication
{
public static void main(String[] args)
{
// Явное конструирование
ICar car = new DefaultCarImpl();
// Использование
car.setPedalPressure(5);
float speed = car.getSpeed();
}
}
public class MyApplication
{
public static void main(String[] args)
{
// Неявное конструирование
Service service = (Service)DependencyManager.get("CarBuilderService");
ICar car = (ICar)service.getService(Car.class);
// Использование
car.setPedalPressure(5);
float speed = car.getSpeed();
}
}
Конфигурационный файл
<service-point id="CarBuilderService">
<invoke-factory>
<construct class="Car">
<service>DefaultCarImpl</service>
<service>DefaultEngineImpl</service>
</construct>
</invoke-factory>
</service-point>
"Клиенты не должны зависеть от интерфейсов, которые они не используют"
Р. Мартин, 1996
Пример namespace utils
.
Cлишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические.
"Наследование должно гарантировать,
что любое свойство, справедливое для супертипа,
должно быть справедливо и для наследников."
Б. Лисков, 1987
Базовый класс
class Bird { // Есть клюв, крылья...
public virtual void fly(); // Птица может летать
};
Наследник 1: Попугай
class Parrot : Bird { // Попугай – птица
public override void fly() { ... }
};
Метод, полиморфно использующий птицу
void PlayWithBird (Bird bird) {
bird.Fly();
}
Метод, полиморфно использующий птицу
void PlayWithBird (Bird bird) {
bird.Fly();
}
Наследник 2: Пингвин
class Penguin : Bird {
public override void fly() {
throw "Пингвины не летают!";
}
};
PlayWithBird
стал неправильным!class Bird {
// есть клюв, крылья...
};
class FlyingBird : Bird {
public virtual void fly(); // птица может летать
};
class Parrot : FlyingBird { // Попугай – летающая птица
public override void fly() { ... }
};
class Penguin : Bird {
// ...
};
class Rectangle {
private double width;
private double height;
public void SetWidth(double w);
public void SetHeight(double h);
public double GetArea();
}
class Square : Rectangle {...}
void Adjust(Rectangle rect)
{
rect.SetWidth(5);
rect.SetHeight(4);
// Чему будет равна площадь?
double area = rect.GetArea();
}
a.Method()
, но не a.b.Method()
player.Accounts[0].Balance.Sum(a => a.ConvertTo(“RUR”));
player.AvailableCash()
Wikipedia
План ответа про каждый из SOLID принципов:
Вопросы:
Вопросы?