OCP (Open Closed Principle) : 개방 폐쇄 원칙
'확장에 대해서는 개방되어 있고, 수정에 대해서는 폐쇄적이어야 한다.' 는 원칙을 말한다.
확장에 대해 개방적이라는 것은 변경 사항이 생길 때, 새로운 코드를 추가해 어플리케이션의 기능을 확장할 수 있다는 것이고,
수정에 대해 폐쇄적이라는 것은 변경 사항이 생길 때, 객체를 직접적으로 수정하는 것은 제한해야 한다는 것을 의미한다.
예를 들어 비밀번호 암호화를 강화하자는 요구사항이 들어왔다고 가정하자.
비밀번호 암호화 강화를 위해 SHA-256 알고리즘을 사용하는 새로운 PasswordEncoder를 만들었다.
class SHA256PasswordEncoder {
companion object {
private const val SHA_256 = "SHA-256"
}
fun encryptPassword(pw: String): String {
val md = MessageDigest.getInstance(SHA_256)
val encodedHash = md.digest(pw.toByteArray(Charsets.UTF_8))
return bytesToHex(encodedHash)
}
private fun bytesToHex(encodedHash: ByteArray): String {
val hexString = StringBuilder(2 * encodedHash.size)
for (hash in encodedHash) {
val hex = Integer.toHexString(0xff and hash.toInt())
if (hex.length == 1) {
hexString.append('0')
}
hexString.append(hex)
}
return hexString.toString()
}
}
그리고 새로운 PasswordEncoder를 사용하려고 보니, UserService에서 encoder 객체를 수정해야 하는 문제가 발생하게 되었다.
class UserService(private val userRepository : UserRepository,
private val passwordEncoder : SHA256PasswordEncoder) {
...
}
}
OCP에 위배되지 않으려면 추상화에 의존해야 한다.
변하지 않는 부분은 고정하고, 변하는 부분을 생략해 추상화하여 변경이 필요한 경우 생략된 부분을 수정해 OCP를 지킬 수 있는 것이다.
위와 같은 상황에서 변하지 않는 부분은 비밀번호 암호화가 필요하다는 것이고, 변하는 것은 암호화 정책(Simple -> SHA256)이다.
UserService는 어떤 암호화 정책을 사용하는지 알 필요없이 passwordEncoder 객체를 통해 암호화된 비밀번호를 받기만 하면 된다.
따라서, UserService가 구체적인 암호화 클래스에 의존하지 않도록 인터페이스에 의존하도록 하여 문제를 해결할 수 있다.
interface PasswordEncoder {
fun encryptPassword(pw: String): String
}
class SHA256PasswordEncoder : PasswordEncoder {
companion object {
private const val SHA_256 = "SHA-256"
}
override fun encryptPassword(pw: String): String {
...
}
private fun bytesToHex(encodedHash: ByteArray): String {
...
}
}
class UserService(private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder) {
fun addUser(email: String, pw: String) {
val encryptedPassword = passwordEncoder.encryptPassword(pw)
val user = User.builder()
.email(email)
.pw(encryptedPassword)
.build()
userRepository.save(user)
}
}
개방 폐쇄 원칙이 얘기하는 추상화는 결국 런타임 의존성과 컴파일타임 의존성과 관련이 있다.
- 컴파일타임 의존성
- 코드를 컴파일 하는 시점에 결정되는 의존성으로 클래스 간 의존성에 해당
- 구현 클래스에 의존하면 컴파일타임 의존성을 갖게 됨
- 결합도가 높고 변경에 유연치 못함
- 런타임 의존성
- 코드를 실행하는 시점에 결정되는 의존성으로 객체 간 의존성에 해당
- 추상화 클래스나 인터페이스에 의존할 때 런타임 의존성을 갖게 됨
- 결합도가 낮고 변경에 유연
현재 UserService는 컴파일 시점에는 추상화된 PasswordEncoder에 의존하여 비밀번호를 암호화해야 한다는 것만 알고 있고, 런타임 시점에 객체를 주입 받아 어떤 암호화 정책과 결합되는지 알 수 있다.
개방 폐쇄 원칙을 잘 지키면, 기존 코드와 클래스에 대한 수정을 최소화하며 앱을 확장할 수 있다.
'프로그래밍 > ect' 카테고리의 다른 글
SOLID, 객체 지향 설계 5원칙 : 인터페이스 분리 원칙 (Interface Segregation Principle) (0) | 2024.06.13 |
---|---|
SOLID, 객체 지향 설계 5원칙 : 리스코프 치환 원칙(Liskov Substitution Principle) (0) | 2024.06.09 |
SOLID, 객체 지향 설계 5원칙 : 단일 책임 원칙(Single Responsibility Principle) (0) | 2024.06.07 |
[MySQL] MySQL 날짜 및 시간 관련 함수 (0) | 2024.05.16 |
[Git] Git(깃)이란 무엇인가 + Android Studio GitHub 연동하기 (0) | 2024.03.08 |