클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 합니다.
기존의 코드를 변경하지 않으면서 기능을 추가 할 수 있어야 합니다
이 원칙의 목적은 새로운 기능을 구현할 때 기존 코드가 깨지지 않도록 하는 것입니다
클래스는 확장할 수 있을때, 자식클래스를 생성할 수 있을때, 기초 행동을 재정의 하고 새로운 메서드 및 필드를 추가하는 등 원하는 모든 작업을 수행할 수 있을때 Open(개방)되어있다고 할 수 있습니다
동시에 클래스가 다른 클래스에 의해 사용될 준비가 100% 되었다면 해등 클래스는 Closed(폐쇄)되었다고 할 수 있습니다
이때 사용될 준비가 되었다는 것은 클래스의 인터페이스가 명확하게 정의되어 있으며 미래에 변경되지 않는다는 뜻입니다
클래스가 이미 개발, 테스트, 검토의 단계를 마쳤고 이미 다른곳에서 사용되는 경우 해당 클래스의 변경은 위험합니다
클래스의 변경이 필요한 경우에는 해당 클래스를 직접 변경하는것이 아닌 자식클래스를 만든 후 원하는 부분들을 재정의해야합니다
그렇게 변하는 기능 및 새로운 기능을 추가하면서도 원래 클래스를 손상하지 않게 됩니다
단 클래스에 버그가 있을 경우 그 문제를 수정하려고 자식클래스를 만들지 말고 그냥 가서 수정하세요
자식클래스는 부모 클래스의 문제들에 대하여 책임을 져서는 안됩니다.
class Handler {
func handle() {
let data = fetchData()
let newData = createNewData(data)
uploadData(newData)
}
func fetchData() -> Data {
// fetchData logic
return Data()
}
func processingData(_ data: Data) -> Data {
// processing logic
return Data()
}
func uploadData(_ data: Data) {
// uploadDatalogic
}
}
class AClass {
let handler = Handler()
func handle() {
handler.handle()
}
}
Handler 객체는 원격 스토리지에서 파일을 fetch 해와서 사용한다
Handler 객체를 BClass 라는 새로운 클래스에서도 사용할려고 한다
BClass 에서는 파일을 원격 스토리지가 아닌 로컬스토리지에서 fetch 해줄려 한다
이를 해결하기 위해 Handler 클래스를 수정해줬다
하지만 이는 변경에 닫혀있어야 하는 개방/폐쇄 원칙에 어긋난다
protocol Handler {
func handle()
}
class RemoteHandler {
func handle() {
let data = fetchData()
let newData = createNewData(data)
uploadData(newData)
}
func fetchData() -> Data {
// fetchData logic
return Data()
}
func processingData(_ data: Data) -> Data {
// processing logic
return Data()
}
func uploadData(_ data: Data) {
// uploadDatalogic
}
}
class LocalHandler {
func handle() {
let data = fetchData()
let newData = createNewData(data)
uploadData(newData)
}
func fetchData() -> Data {
// fetchData logic
return Data()
}
func processingData(_ data: Data) -> Data {
// processing logic
return Data()
}
func uploadData(_ data: Data) {
// uploadDatalogic
}
}
class AnotherHandler: Handler {
override func fetchData(_ flag: Bool) -> Data {
// fetchData logic
return Data()
}
}
class BClass {
let handler: Handler
init(handler: Handler) {
self.handler = handler
}
func handle() {
handler.handle()
}
}
class BClass {
let handler: Handler
init(handler: Handler) {
self.handler = handler
}
func handle() {
handler.handle()
}
}
Handler 라는 프로토콜이 있고 실제 메서드 구현은 해당 프로토콜을 채택한 구상객체에서 하게 된다
이제 새로운 handler 메서드를 구현할경우 이미 구현된 클래스를 변경할 필요 없이 Handler 프로토콜에서 새로운 클래스를 파생할 수 있다
struct Dog {
let name: String
let age: Int
}
class Zoo {
var animals: [Dog] = []
func addAnimal(_ animal: Dog) {
animals.append(dog)
}
func hello() {
print("멍멍")
}
}
동물원 객체가 있다
해당 동물원은 예산이 없어서 강아지밖에 없다
따라서 현재 코드는 기능에 문제가 없다
하지만 나중에 동물원이 돈을 많이 벌어서 새로운 동물들을 들여온다고 생각해보자
현재 Zoo 객체는 강아지 타입밖에 받아들일 수 없어 필연적으로 Zoo 객체를 수정해야 한다
protocol Animal {
var name: String { get }
var age: Int { get set }
func hello()
}
struct Dog: Animal {
var name: String
var age: Int
func hello() {
print("멍멍")
}
}
struct Tiger: Animal {
var name: String
var age: Int
func hello() {
print("어흥")
}
}
class Zoo {
var animals: [Animal] = []
func add(_ animal: Animal) {
animals.append(animal)
}
func hello(_ animal: Animal) {
animal.hello()
}
}
Animal 프로토콜을 생성하고 실제 동물 구상 객체들이 Animal 프로토콜을 채택하게 해 주었다
Zoo 클래스는 동물 구상 객체에 의존하지 않고 Animal 프로토콜을 의존해주도록 했다
이제 동물원에 새로운 동물을 들여오더라 하더라도 Animal 이라는 프로토콜을 채택하고 있으면 Zoo 객체에서 받아들일 수 있기때문에 Zoo 객체를 수정해줄 필요가 없어진다