[안드로이드스튜디오_문화][SOLID 원칙]

기말 지하기포·2023년 11월 20일
0

'SOLID 원칙'

SOLID 원칙이란 소프트웨어 엔지니어링에서 객체 지향 설계를 보다 이해하기 쉽고 유연하며 유지 관리하기 쉽게 만들기 위한 5가지 설계 원칙 의 약어 입니다.

SRP[Single Responsibility Principle]

-각각의 모듈 , 클래스 , 함수는 하나의 책임만 가져야한다는 의미이다.
위에서 말한 책임이란 해당 모듈 , 클래스 , 함수를 수정하고자 하는 이유를 의미한다.

-즉, 어떤 모듈 , 클래스 , 함수가 존재한다는 것은 존재 목적이라는 것이 있을 것이고 , 우리가 모듈 , 클래스 , 함수를 생성할 때 단 하나의 존재 목적만을 가지고 만들어야 한다는 것이다.

-존재목적이 변경될 때 앱에 미치는 영향이 지나치게 많으면 유지보수가 너무 힘들기에 해당 모듈 , 클래스 , 함수의 변경 사항이 다른 객체들에게 미치는 영향을 최소화 시키는 방향으로 모듈 , 클래스 , 함수의 존재 목적을 지정해줘야 한다.

-해당 모듈 , 함수 , 클래스를 수정해야 하는 이유 즉, 책임영역이 확실해지기 때문에 하나의 책임 영역의 변경이 다른 책임 영역의 변경을 발생시키지 않는다. 따라서 유지보수가 편리하고 , 재사용성이 높다.

-정리 : SRP란 내가 앱에서 가지고 있는 역할이 단, 하나여야 한다는 것

OCP[Open Closed Principle]

-각각의 모듈 , 클래스 , 함수는 확장에는 열려있고 , 수정에는 닫혀 있어야한다. 즉 , 기존의 코드는 수정하지 않으면서 역할을 가진 모듈 , 클래스 , 함수를 추가 할 수 있어야 한다는 뜻.

-보다 더 자세히 말하면 , 역할을 가진 임의의 알파를 추가 할 때에 기존의 역할을 구성하는 코드들에는 변화가 없고 (코드 수정에 닫혀있음), 알파를 구성하는 코드들만 추가적으로 작성하면 된다. (코드 확장에는 열려있음)

-그렇다면 어떻게 해야지 코드 수정 없이 임의의 알파를 구성하는 코드를 자유롭게 작성 할 수 있을까? 정답은 추상화이다. 즉, 추상클래스 또는 인터페이스의 구현을 통해 임의의 알파가 애플리케이션에서 가지는 역할을 코드 상에서 구현하면 코드 확장에 열려있게 된다. 추상화를 구체화 시킴으로서 코드를 확장하는 것이므로 기존의 코드 수정에는 닫혀있게 된다.

-정리 : OCP란 , 추상화를 구체화함으로서(추상화를 구현) 임의의 알파를 구성하는 코드를 추가하기 때문에 코드 확장에 자유로울 수 있고, 기존의 코드에 대한 수정에는 닫혀있을 수 있다. 따라서

LSP[Liskov Substitution Principle]

-Type S가 Type T의 서브타입 일 때 , T의 객체를 S의 객체로 대체해도 프로그램의 기능에 어떠한 영향도 끼칠 수 없다는 것이다.

-즉, 임의의 베타가 임의의 알파를 상속 받았다면 , 알파의 객체를 사용하다가 베타의 객체로 변경하여 사용하여도 코드가 여전히 잘 작동해야 한다는 것이다.

-상속시 하위 개념은 상위 개념의 수행 영역과 일치해야 한다는 의미로서 수행 내용이 다르더라도 수행 영역은 상위 하위 간에 반드시 일치해야 한다. 만약 일치하지 않는다면 상속을 받으면 안된다.

-LSP 원칙을 지키기 위한 규칙은 메소드 오버로딩 시 메서드 규약을 반드시 유지해야 된다는 것이다. 그러나 필연적으로 메서드 규약을 벗어난 코드를 작성해야 할 때도 있다. 이럴 때는 메서드를 interface 블락안으로 넣어서 interface를 implement 시키는 방식으로 사용하면 된다. (상속 대신 합성을 사용하라는 의미야.)

-정리 : LSP란 상위 개념의 객체 자리를 하위 개념의 객체로 치환하여도 코드 수행에 이상이 없도록 하위 개념이 상위 개념의 수행 영역과 일치하게 상속을 잘 받아야 한다는 것이다. (수행 동작은 달라도 된다. 단, 반드시 수행 영역은 서로 일치해야 한다.)

ISP[Interface Segregation Principle]

-클래스는 자신이 사용하지 않는 인터페이스는 구현하면 안 된다는 것이다. 즉, '하나의 일반적인 범용 인터페이스'보다는 '여러개의 구체적인 인터페이스가 더 낫다'라는 것이다. 그러므로 인터페이스를 사용 목적에 맞게 최소 단위로 분리해야 한다는 것이다.

-그러나 이 인터페이스가 변경되거나 인터페이스를 한 번더 분리하게 된다며 해당 인터페이스를 구현하는 구현부의 코드를 전부 변경해야 되기 때문에 이 인터페이스는 절대로 변하면 안되는 정책이다.

-정리 : ISP는 인터페이스를 최소단위로 분리함으로서 , 임의의 알파가 인터페이스를 구현할 때 불필요한 구현을 막을 수 있다. (단 구현하는 내용은 각기 다르므로 다양한 기능을 갖는 것은 가능하다 => SRP와의 차이점 : SRP는 하나의 기능만을 갖는다. , cf : SRP 만족이 반드시 ISP를 만족시키는 것은 아니야.)

DIP[Dependency Inversion Principle]

  • 상위계층(정책 ex : UseCase)이 하위계층(세부사항 ex : RepositoryImpl)에 의존하는 기존의존관계로부터 벗어나서 , 상위계층과 하위계층의 사이에 추상화 된 계층을 넣어서 상위 계층과 하위 계층 모두가 추상화된 계층에 의존하게 만드는 것이다. 이로인해 화살표의 방향이 변경되어서 '의존성 역전 원칙'이라고 불린다.
  • 조금 어려운 개념이므로 예시를 든다면 [데이터 저장하는 인터페이스] , [데이터 저장하는 인터페이스를 구현한 클래스] , [데이터를 처리하는 클래스] 이런 3가지의 파일이 있을 때 , 각각을 1 , 2 , 3으로 치환한다.
  • 그러면 2->1<-3 이라는 의존관계가 나타나게 된다. 이게 가능한 이유는 LSP 원칙이 있기 때문이다.
(2)
class FileDataRepository : DataRepository {
    override fun save(data: String) {
        println("Data '$data' is saved to a file.")
        // 파일 시스템에 데이터 저장하는 로직
    }
}
(1)
interface DataRepository {
    fun save(data: String)
}
(3)
class DataProcessor(private val repository: DataRepository) {
    fun process(data: String) {
        println("Processing data: $data")
        repository.save(data)
    }
}
fun main() {
    val fileRepository = FileDataRepository()
    
    // LSP 원칙이 있기 때문에 , DataRepository를 구현한 FileDataRepository가
    // DataProcessor()의 Parameter 자리에 들어갈 수 있는 것이야.
    val dataProcessor = DataProcessor(fileRepository)

    dataProcessor.process("Some data")
}
profile
포기하지 말기

0개의 댓글