«Одно поручение. Всего одно.» — Локи говорит Скурджу в фильме «Тор: Рагнарёк».
На самом верхнем уровне мы декомпозируем систему на пакаджи. В соответствии с этим принципом каждый пакадж должен заниматься какой-то отдельной вещью.
Дальше пакадж мы делим на структуры с набором методов. Каждая структура и связанные с ней методы несут отвественность за какую-то более специфическую задачу внутри пакаджа.
Каждый метод структуры выполняет какую-то одну единственную задачу.
Представим, что у нас есть стукртура реализующая интерфейс IAnimal
type IAnimal interface {
GetAnimal() string
SaveAnimal()
}
type Animal struct {
name string
}
func (animal *Animal) GetAnimal() string {
return animal.name
}
func (animal *Animal) SaveAnimal() {
// impl
}
Стуктура Animal
, представленная здесь, описывает какое-то животное.
Эта стуктура нарушает принцип единственной ответственности.
Как именно нарушается этот принцип?
В соответствии с принципом единственной ответственности структура должена решать лишь какую-то одну задачу.
Она же решает две, занимаясь работой с хранилищем данных в методе SaveAnimal
и манипулируя свойствами объекта в методе GetAnimal
.
Как такая структура класса может привести к проблемам?
Если изменится порядок работы с хранилищем данных, используемым приложением, то придётся вносить изменения во все структуры, работающие с хранилищем. Такая архитектура не отличается гибкостью, изменения одних подсистем затрагивают другие, что напоминает эффект домино.
Для того чтобы привести вышеприведённый код в соответствие с принципом единственной ответственности, создадим ещё одну стуктуру, единственной задачей которой является работа с хранилищем, в частности — сохранение в нём объектов структуры.
type IAnimal interface {
GetAnimal() string
}
type IAnimalStorage interface {
Save(animal Animal)
Get(animal Animal)
}
type AnimalStorage struct {}
func (storage *AnimalStorage) Save(animal Animal) {
// impl
}
func (storage *AnimalStorage) Get(animal Animal) {
// impl
}
type Animal struct {
name string
}
func (animal *Animal) GetName() string {
return animal.name
}
Вот что по этому поводу говорит Стив Фентон:
Проектируя классы, мы должны стремиться к тому, чтобы объединять родственные компоненты, то есть такие, изменения в которых происходят по одним и тем же причинам. Нам следует стараться разделять компоненты, изменения в которых вызывают различные причины.
Правильное применение принципа единственной ответственности приводит к высокой степени связности элементов внутри модуля, то есть к тому, что задачи, решаемые внутри него, хорошо соответствуют его главной цели.