개방/폐쇄 원칙

김지민·2023년 5월 28일
0

Solid

목록 보기
2/5

개방/폐쇄 원칙 (Open/Closed Principle)

클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 합니다.

개념

기존의 코드를 변경하지 않으면서 기능을 추가 할 수 있어야 합니다

이 원칙의 목적은 새로운 기능을 구현할 때 기존 코드가 깨지지 않도록 하는 것입니다
클래스는 확장할 수 있을때, 자식클래스를 생성할 수 있을때, 기초 행동을 재정의 하고 새로운 메서드 및 필드를 추가하는 등 원하는 모든 작업을 수행할 수 있을때 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 객체를 수정해줄 필요가 없어진다


확장에 열려있다

  • 모듈의 확장성을 보장한다
  • 새로운 변경사항이 발생할 경우 유연하게 코드를 추가함으로써 애플리케이션의 기능을 큰 힘을 들이지 않고 확장할 수 있다

변경에 닫혀있다

  • 객체를 직접적으로 수정하는것은 제한해야 한다
  • 새로운 변경 사항이 발생했을때 객체를 직접 수정해야 한다면 새로운 변경사항에 대해 유연하게 대응할 수 없는 애플리케이션이다
  • 따라서 객체를 직접 수정하지 않고도 변경사항을 적용할 수 있도록 설계해야 한다
profile
iOS 신입으로 일하고 있습니다

0개의 댓글