DesignPattern 시리즈04 리치 교체 원칙

4814 단어
1. 내용 도입-계승 체계에 대한 사고
상속에서 부류에서 이미 실현된 방법은 일종의 계약이나 규범이라고 할 수 있고 부류는 변경(재작성)을 해서는 안 된다.그러나 이 점은 강제 요구가 아니기 때문에 자류를 다시 쓸 때 상속 체계에 파괴를 초래할 수 있다.이 동시에 계승이 편리함을 가져올 때도 폐단이 있다. 프로그램에 침입성을 가져오고 대상 간의 결합성을 증가하며 이식성이 낮다.기본 클래스를 수정할 때, 하위 클래스는 상응하는 수정을 해야 한다.그렇다면 어떻게 하면 계승의 장점을 유지하면서 단점이 프로그램에 미치는 영향을 줄일 수 있을까?바로 우리가 논의해야 할 주인공인'리씨 교체 원칙'이다.
2. 이씨 교체 원칙의 정의
1. 첫 번째 정의
만약에 모든 유형이 S인 대상 o1에 대해 T인 대상 o2를 사용하고 T로 정의된 모든 프로그램 P가 모든 대상 o1이 o2로 바뀔 때 프로그램 P의 행위는 변화가 없다. 그러면 유형 S는 유형 T의 하위 유형이다.
2. 두 번째 정의
모든 인용 기류의 부분은 그 자류를 투명하게 사용하여 교체해야 한다.
두 번째 유형은 통속적이고 이해하기 쉽다. 즉, 부류가 나타나는 곳이라면 모두 그 자류로 교체할 수 있어야 한다는 것이다.그러나 반대로 반드시 성립되는 것은 아니다. 즉, 자류가 있는 곳을 부류로 바꾸는 것이 반드시 성립되는 것은 아니다.
정의에 따라 다음과 같은 사항이 요약됩니다.
1. 자류는 부류 정의의 추상적인 방법을 실현해야 한다.부류가 이미 실현된 비추상적인 방법에 대해서는 다시 써서는 안 된다.
코드와 결합하여 이해하기
public abstract class SuperClass {
    public abstract void sayHi();
    public void doSomething() {
        System.out.println("     ...");
    }
    
    //          
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("result = " + result);
        return result;
    }
}

public class SubClass extends SuperClass {
    @Override
    public void sayHi() {
        System.out.println("        sayHi  ...");
    }

    //       
    public void selfMethod() {
        System.out.println("       ");
    }

    //                 
    @Override
    public int add(int i, int j) {
        int result = i - j;
        System.out.println("result = " + result);
        return result;
    }

}

public class Client {
    public static void main(String[] args) {
        SuperClass clazz = new SubClass();
        clazz.doSomething();
        SubClass subClass = new SubClass();
        //         
        subClass.selfMethod();
        SuperClass superClass = (SuperClass) subClass;
        superClass.add(11,22);
    }
}

//      :
     ...
       
result = -11

기본 클래스를 계승할 때, 컴파일러는 기본 클래스의 추상적인 방법을 요구할 것입니다. 그렇지 않으면 오류가 발생할 수 있습니다.그러나 이미 실현된 방법에 대해 컴파일러는 강제로 다시 쓰게 하지 않는다. (이렇게 하는 것도 추천하지 않는다.) 위의 데모에서 하위 클래스는 부모 클래스의dd 방법을 다시 썼고 호출할 때 오류가 발생했다. (기본 클래스가 정의한 덧셈 논리, 이불 클래스는 뺄셈으로 다시 쓴다.)이렇게 해서 부류가 출현한 곳에서는 자류로 완전히 바꿀 수 없으며'리씨교체원칙'에 위배된다.
2. 자류는 자신만의 방법이 있을 수 있다.
위의 demo에서 하위 클래스는 특유의 방법인selfMethod()를 정의하여 다른 업무 논리를 실현할 수 있다.
   //       
    public void selfMethod() {
        System.out.println("       ");
    }

3. 부류를 덮어쓰거나 부류를 실현하는 방법이 있을 때 선행 조건(형삼)은 부류 방법의 입력 매개 변수보다 더 느슨할 수 있다.
데모:
public class Father {
    public Collection doSomething(HashMap map) {
        System.out.println("     ...");
        return map.values();
    }
}

public class Son extends Father {
     public Collection doSomething(Map map) {
        System.out.println("     ...");
        return map.values();
    }
}

public class Client1 {
    public static void main(String[] args) {
        invoke();
    }

    public static void invoke() {
//        Father clazz = new Father();
        Son clazz = new Son();
        HashMap hashMap = new HashMap();
        clazz.doSothing(hashMap);
    }

//    :    ...

데모에서 기본 클래스는 DoSomething 방법을 정의했다. 받아들인 인삼 유형은 HashMap이고 하위 클래스는 기본 클래스를 다시 불러오는 이 방법을 정의했으며 받아들인 인삼 유형을 Map으로 확장했다.그러나 부류가 나타난 곳에서 자류로 바뀐 후 운행의 결과는 변하지 않는다는 것을 발견했다.즉, 하위 클래스 대상은 방법이 실행될 때 부모 클래스 대상으로 바뀌고 다시 불러오는 방법은 실행되지 않는다는 것이다.하위 클래스를 실행하려면 다시 써야 합니다. 이것은 정확합니다.반대로 하면?다음은 반증을 통해 세 번째 점을 증명한다. 자류의 방법 중의 선행 조건이 부류보다 작을 때 상황은 어떻게 될까?부류와 자류에서 이 두 방법을 다시 정의하고 자류에 부합되는 선행 조건은 부류보다 작으면 된다.
   //       ,       Map
    public Collection doSomething(Map map) {
        System.out.println("     ...");
        return map.values();
    }

   //    ,       HashMap
    public Collection doSomething(HashMap map) {
        System.out.println("     ...");
        return map.values();
    }

클라이언트의 invoke () 방법에서Father 형식을 통해doSomething 방법을 호출할 때 실행 결과는 다음과 같습니다:// : ****객체를 Son 유형으로 바꿀 때, 결과를 실행할 때:
 public static void invoke() {
//        Father clazz = new Father();
        Son clazz = new Son();
        HashMap hashMap = new HashMap();
        clazz.doSothing(hashMap);
    }

//    :      ...

우리는 하위 클래스가 하위 클래스를 다시 쓰는 방법은 없지만 하위 클래스의 방법은 실행되었다(이것은 호출 방법을 사용할 때 매개 변수의 유형이 일치하는 방법, 즉 재부팅 방법을 우선선택하고 찾지 못하면 유형이 인삼인 기본 클래스를 찾는 방법을 찾았기 때문이다). 그러나 이 현상은 논리적으로 잘못된 것이다.
4. 자류의 방법이 부류의 추상적인 방법을 실현할 때 방법의 후치 조건(즉 방법의 반환값)이 부류보다 더욱 엄격하다.
이 점은 재작성의 요구 중 하나로 여기서 코드로 보여 준다.
모범 사례
앞의 예시에서, 우리는 하위 클래스 SubClass에서 기본 클래스 SupClass가 이미 실현한 방법add () 를 부주의로 다시 써서 논리 오류가 발생했다.
1. 실제 사용에서 우리는 부류가 이미 실현한 방법을 다시 쓰는 것을 최대한 피해야 한다.
2. 적당한 상황에서 사용 상속을 줄이고 집합, 조합, 의존 등을 많이 사용하여 문제를 해결한다.

좋은 웹페이지 즐겨찾기