컴퓨터 프로그램을 구별된 부분으로 분리시키는 디자인 원칙으로, 각 부문은 개개의 관심사를 해결합니다.
관심사란 컴퓨터 프로그램 코드에 영향을 미치는 정보의 집합, 관심사는 코드 최적화가 필요한 하드웨어의 세세한 부분만큼 포괄적이거나, 시작할 클래스의 이름처럼 구체적일 수 있습니다. SoC를 구현하는 프로그램은 모듈러 프로그램이라고 부릅니다.
관심사 분리를 이용하면 프로그램의 설계, 디플로이, 이용의 일부 관점에 더 높은 정도의 자유가 생깁니다. 이 가운데 일반적인 것은 코드의 단순화 및 유지보수의 더 높은 수준의 자유입니다. 관심사가 잘 분리될 때 독립적인 개발과 업그레이드 외에도 모듈 재사용을 위한 더 높은 정도의 자유가 있습니다. 모듈이 인터페이스 뒤에서 이러한 관심사의 세세한 부분을 숨기기 때문에 자유도가 높아짐으로써 다른 부분의 세세한 사항을 모르더라도, 또 해당 부분들에 상응하는 변경을 취하지 않더라도 하나의 관심사의 코드 부분을 개선하거나 수정할 수 있게 됩니다. 또, 모듈은 각기 다른 버전의 인터페이스를 노출할 수 있으며, 이를 통해 중간의 기능 손실 없이 단편적인 방식으로 복잡한 시스템을 업그레이드하는 자유도를 높여줍니다.
관심사 분리는 추상화의 일종입니다. 대부분의 추상화에서처럼 인터페이스의 추가는 필수이며 실행에 쓰이는 더 순수한 코드가 있는 것이 일반적입니다. 그러므로 잘 분리된 관심사의 여러 장점에도 불구하고 관련 실행에 따른 불이익이 있기도 합니다.
class Journal: CustomStringConvertible {
var entries = [String]()
var count = 0
func addEntry(_ text: String) -> Int {
count += 1
entries.append("\(count) : \(text)")
return count - 1
}
func removeEntry(_ index: Int)
{
entries.remove(at: index)
}
var description: String
{
return entries.joined(separator: "\n")
}
func save(_ filename: String, _ overwrite: Bool = false)
{
// save to a file
}
func load(_ filename: String) {}
func load(_ url: URL) {}
}
class Persistence
{
func saveToFile(_ journal: Journal,
_ filenmae: String,
_ overwriet: Bool = false)
{
}
}
func main()
{
let j = Journal()
let _ = j.addEntry("I cried today")
let bug = j.addEntry("I ate a bug")
print(j)
j.removeEntry(bug)
print("===")
print(j)
let p = Persistence()
let filename = "/mnt/c/ffafe"
p.saveToFile(j, filename)
}
main()
위 코드에서 Journal 클래스에선 데이터를 읽고 쓰는 역할을
Persistence 클래스에선 파일을 저장하는 역할을 가지고 있는 것을 알 수 있습니다.
// Specification
protocol Specification
{
associatedtype T
func isSatisfied(_ item: T) -> Bool
}
protocol Filter
{
associatedtype T
func filter<Spec: Specification>(_ items: [T], _ spec: Spec) -> [T]
where Spec.T == T;
}
class AndSpecification<T,
SpecA: Specification,
SpecB: Specification> : Specification
where SpecA.T == SpecB.T, T == SpecA.T, T == SpecB.T
{
let first: SpecA
let second: SpecB
init(_ first: SpecA, _ second: SpecB)
{
self.first = first
self.second = second
}
func isSatisfied(_ item: T) -> Bool
{
return first.isSatisfied(item) && second.isSatisfied(item)
}
}
class BetterFilter : Filter
{
typealias T = Product
func filter<Spec>(_ items: [Product], _ spec: Spec) -> [Product] where Spec : Specification, Product == Spec.T
{
var result = [Product]()
for i in items
{
if spec.isSatisfied(i)
{
result.append(i)
}
}
return result
}
}
class ColorSpecification : Specification
{
typealias T = Product
let color: Color
init(_ color: Color)
{
self.color = color
}
func isSatisfied(_ item: Product) -> Bool
{
return item.color == color
}
}
class SizeSpecification : Specification
{
typealias T = Product
let size: Size
init(_ size: Size)
{
self.size = size
}
func isSatisfied(_ item: Product) -> Bool
{
return item.size == size
}
}
let bf = BetterFilter()
print("Large blue items")
for p in bf.filter(products,
AndSpecification(ColorSpecification.init(.blue)
, SizeSpecification(.large))
)
{
print(" - \(p.name) is Large and Blue")
}
class Rectangle : CustomStringConvertible
{
var _width = 0
var _height = 0
var width: Int
{
get { return _width }
set(value) { _width = value }
}
var height: Int
{
get { return _height }
set(value) { _height = value }
}
init(){}
init(_ width: Int, _ height: Int)
{
_width = width
_height = height
}
var area: Int
{
return _width * height
}
public var description: String
{
return "widht : \(width), height: \(height)"
}
}
class Square : Rectangle
{
override var width: Int
{
get {return _width}
set(value)
{
_width = value
_height = value
}
}
override var height: Int
{
get {return _height}
set(value)
{
_width = value
_height = value
}
}
}
func setAndMeasure(_ rc: Rectangle)
{
rc.width = 3
rc.height = 4
print("Expected area to be 12 but got \(rc.area)")
}
func main()
{
let rc = Rectangle()
setAndMeasure(rc)
let sr = Square()
setAndMeasure(sr)
}
protocol Machine
{
func print(d: Document)
func scan(d: Document)
func fax(d: Document)
}
이렇게 많은 기능이 있는 프로토콜을
protocol Printer
{
func print(d: Document)
}
protocol Scanner
{
func scan(d: Document)
}
protocol Fax
{
func fax(d: Document)
}
이런식으로 나눠줄 수 있고
protocol MultiFunctionDevice : Printer, Scanner, Fax
{
}
이렇게 합쳐줄 수 도 있습니다.
protocol RelationshipBrowser
{
func findAllChildrenOf(_ name: String) -> [Person]
}
class Relationships : RelationshipBrowser // low - level
{
private var relations = [(Person, Relationship, Person)]()
func addParentAndChild(_ p: Person, _ c: Person)
{
relations.append((p, .parent, c))
relations.append((c, .child, p))
}
func findAllChildrenOf(_ name: String) -> [Person] {
return relations
.filter { $0.name == name && $1 == .parent && $2 === $2 }
.map {$2}
}
}
class Research // high - level
{
// init(_ relationships: Relationships)
// {
// let relations = relationships.relations
// for r in relations where r.0.name == "John" && r.1 == .parent
// {
// print("John has a child called \(r.2.name)")
// }
// }
init(_ browser: RelationshipBrowser)
{
for p in browser.findAllChildrenOf("John")
{
print("John has a child called \(p.name)")
}
}
}
high-level 에서 low-level 에 관여할 수 없다