JAVA 프로그래밍 사상(4) - 다중(一)

27379 단어
다태
대상을 대상으로 하는 프로그램 설계 언어에서 다태는 데이터 추상과 계승에 이어 세 번째 기본 유형이다.
다태는 분리를 통해 무엇을 하고 어떻게 하는지를 다른 각도에서 인터페이스와 실현을 분리한다.멀티태스킹은 코드의 조직 구조와 읽기 가능성을 개선할 뿐만 아니라 확장 가능한 프로그램도 만들 수 있다.
상향 전환을 재론하다
코드
//: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music;

public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~

//: polymorphism/music/Instrument.java
package polymorphism.music;
import static net.mindview.util.Print.*;

class Instrument {
  public void play(Note n) {
    print("Instrument.play()");
  }
}
 ///:~

 //: polymorphism/music/Wind.java
package polymorphism.music;

// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
  // Redefine interface method:
  public void play(Note n) {
    System.out.println("Wind.play() " + n);
  }
} ///:~

//: polymorphism/music/Music.java
// Inheritance & upcasting.
package polymorphism.music;

public class Music {
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); // Upcasting
  }
} /* Output: Wind.play() MIDDLE_C *///:~

Music.tune () 방법은 Instrument 인용을 받아들일 뿐만 아니라, Instrument에서 내보낸 클래스도 받아들일 수 있습니다.예를 들어 Wind는 Instrument의 내보내기 클래스입니다. Wind 인용이tune () 방법식에 전달되면 어떤 종류의 변환도 필요하지 않습니다.이렇게 하는 것은 허용된다. 윈드가 Instrument에서 계승되기 때문에, Instrument 인터페이스는 반드시 윈드에 존재한다. 윈드에서 Instrument로 전환하면 인터페이스를 축소할 수 있지만, Instrument의 모든 인터페이스보다 좁지는 않다.
개체 유형 잊음
왜 모든 사람들이 고의로 상대방의 유형을 잊어버릴까요?상향 전환을 진행할 때 이런 상황이 발생한다.tune () 방법으로 윈드 인용을 자신의 매개 변수로 받아들이면 더욱 직관적일 것 같습니다.그러나 이렇게 하면 중요한 문제를 일으킬 수 있다. 만약 그렇게 한다면, Instrument의 모든 유형에 새로운tune 방법을 작성해야 한다.만약 이렇다면, 우리가 새로운 유형을 추가할 때 코드를 대량으로 늘려야 한다.
예를 들면
//: polymorphism/music/Music2.java
// Overloading instead of upcasting.
package polymorphism.music;
import static net.mindview.util.Print.*;

class Stringed extends Instrument {
  public void play(Note n) {
    print("Stringed.play() " + n);
  }
}

class Brass extends Instrument {
  public void play(Note n) {
    print("Brass.play() " + n);
  }
}

public class Music2 {
  public static void tune(Wind i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Stringed i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Brass i) {
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    Brass frenchHorn = new Brass();
    tune(flute); // No upcasting
    tune(violin);
    tune(frenchHorn);
  }
} /* Output: Wind.play() MIDDLE_C Stringed.play() MIDDLE_C Brass.play() MIDDLE_C *///:~

이렇게 하면 되지만, 단점이 하나 있다. 모든 새로운 Instrument 클래스를 추가하기 위해 특정한 유형을 작성하는 방법이어야 한다.이것은 시작할 때 더 많은 프로그래밍이 필요하다는 것을 의미하며, 이후에tune () 과 같은 새로운 방법을 추가하거나 Instrument에서 내보낸 새로운 종류를 추가하려면 많은 작업이 필요하다는 것을 의미한다.또한, 만약 우리가 어떤 방법을 다시 불러오는 것을 잊어버리면, 컴파일러는 어떠한 오류 정보도 되돌려주지 않아서, 유형에 대한 전체 처리 과정을 제어하기 어려울 것이다.
만약 우리가 이런 간단한 방법만 쓴다면, 그것은 특수한 내보내기 클래스가 아니라 기본 클래스를 매개 변수로 받아들일 것이다.이렇게 하면 상황이 좋아질까요?즉, 만약 우리가 내보내기 클래스의 존재를 막론하고 작성한 코드가 기본 클래스와 접촉만 한다면 더욱 좋지 않겠는가?이것이 바로 다태적으로 허용되는 것이다.
전기.
코드 관찰
public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDLE_C);
}

이것은 Instrument 인용을 받아들인다. 그러면 컴파일러는 이 Instrument 인용이 Brass 대상이나 다른 대상이 아니라 윈드 대상을 가리키는 것을 어떻게 알았을까. 사실상 컴파일러는 알 수 없고, 이것은 귀속과 관련이 있다.
방법 호출 귀속
하나의 방법을 같은 방법으로 호출하여 주체를 연결시키는 것을 귀속이라고 한다.만약 프로그램이 실행되기 전에 귀속을 진행한다면 전기 귀속이라고 한다.이것은 프로세스를 위한 언어에서 기본 귀속 방식을 선택할 필요가 없습니다.예를 들어 C++는 단지 한 가지 방법으로 호출할 수 있는데 그것이 바로 전기 귀속이다.
그러나 이것은 위 코드의 곤혹을 해결하기에 충분하지 않다. 해결 방법은 후기 귀속이다. 그 의미는 운행할 때 대상의 유형에 따라 귀속을 하고 후기 귀속을 동적 귀속 또는 운행할 때 귀속이라고 한다.여기서 컴파일러는 줄곧 대상의 유형을 알지 못하지만, 방법 호출 메커니즘은 정확한 방법체를 찾아 호출할 수 있다.후기 귀속 메커니즘은 컴파일 언어에 따라 다르기 때문에 어쨌든 대상에 특정한 유형 정보를 배치해야 한다.
자바는 Static 방법과final 방법(private 방법은final에 속함)을 제외한 모든 방법이 후기 귀속이다. 이것은 일반적인 상황에서 후기 귀속을 하는지 여부를 판정할 필요가 없다는 것을 의미한다. 자바는 자동으로 발생한다.
어떤 방법을final 방법으로 설명하면 동적 연결을 효과적으로 닫을 수 있습니다. 그러면 컴파일러는final 방법으로 호출하여 더욱 효과적인 코드를 생성할 수 있습니다.
올바른 행동이 일어나다
자바의 모든 방법이 동적 연결을 통해 멀티태스킹을 실현한다는 사실을 알게 되면, 우리는 기본 클래스와만 접촉하는 프로그램 코드를 작성할 수 있고, 이 코드들은 모든 내보내기 클래스에 정확하게 실행될 수 있다.
//: polymorphism/shape/Shape.java
package polymorphism.shape;

public class Shape {
  public void draw() {}
  public void erase() {}
} ///:~

//: polymorphism/shape/Circle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;

public class Circle extends Shape {
  public void draw() { print("Circle.draw()"); }
  public void erase() { print("Circle.erase()"); }
} ///:~

//: polymorphism/shape/Triangle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;

public class Triangle extends Shape {
  public void draw() { print("Triangle.draw()"); }
  public void erase() { print("Triangle.erase()"); }
} ///:~

//: polymorphism/shape/Square.java
package polymorphism.shape;
import static net.mindview.util.Print.*;

public class Square extends Shape {
  public void draw() { print("Square.draw()"); }
  public void erase() { print("Square.erase()"); }
} ///:~

//: polymorphism/shape/RandomShapeGenerator.java
// A "factory" that randomly creates shapes.
package polymorphism.shape;
import java.util.*;

public class RandomShapeGenerator {
  private Random rand = new Random(47);
  public Shape next() {
    switch(rand.nextInt(3)) {
      default:
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
} ///:~

//: polymorphism/Shapes.java
// Polymorphism in Java.
import polymorphism.shape.*;

public class Shapes {
  private static RandomShapeGenerator gen =
    new RandomShapeGenerator();
  public static void main(String[] args) {
    Shape[] s = new Shape[9];
    // Fill up the array with shapes:
    for(int i = 0; i < s.length; i++)
      s[i] = gen.next();
    // Make polymorphic method calls:
    for(Shape shp : s)
      shp.draw();
  }
} /* Output: Triangle.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Circle.draw() *///:~

이 코드는 전형적인 다중 응용이다.
확장성
다중 메커니즘이 있기 때문에, 우리는 자신의 수요에 따라 시스템에 임의의 새로운 유형을 추가할 수 있으며, 코드를 수정할 필요가 없다.좋은 OOP 프로그램에서 대다수 또는 모든 방법은 기본 인터페이스와만 통신한다.이러한 프로그램은 확장할 수 있다. 왜냐하면 우리는 일반적인 기본 클래스에서 새로운 데이터 형식을 계승하여 새로운 기능을 추가할 수 있기 때문이다. 기본 인터페이스를 조작하는 방법은 어떠한 변동도 없이 새로운 클래스에 응용할 수 있기 때문이다.
Instrument의 예를 살펴보겠습니다.
//: polymorphism/music3/Music3.java
// An extensible program.
package polymorphism.music3;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;

class Instrument {
  void play(Note n) { print("Instrument.play() " + n); }
  String what() { return "Instrument"; }
  void adjust() { print("Adjusting Instrument"); }
}

class Wind extends Instrument {
  void play(Note n) { print("Wind.play() " + n); }
  String what() { return "Wind"; }
  void adjust() { print("Adjusting Wind"); }
}    

class Percussion extends Instrument {
  void play(Note n) { print("Percussion.play() " + n); }
  String what() { return "Percussion"; }
  void adjust() { print("Adjusting Percussion"); }
}

class Stringed extends Instrument {
  void play(Note n) { print("Stringed.play() " + n); }
  String what() { return "Stringed"; }
  void adjust() { print("Adjusting Stringed"); }
}

class Brass extends Wind {
  void play(Note n) { print("Brass.play() " + n); }
  void adjust() { print("Adjusting Brass"); }
}

class Woodwind extends Wind {
  void play(Note n) { print("Woodwind.play() " + n); }
  String what() { return "Woodwind"; }
}    

public class Music3 {
  // Doesn't care about type, so new types
  // added to the system still work right:
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void tuneAll(Instrument[] e) {
    for(Instrument i : e)
      tune(i);
  }    
  public static void main(String[] args) {
    // Upcasting during addition to the array:
    Instrument[] orchestra = {
      new Wind(),
      new Percussion(),
      new Stringed(),
      new Brass(),
      new Woodwind()
    };
    tuneAll(orchestra);
  }
} /* Output: Wind.play() MIDDLE_C Percussion.play() MIDDLE_C Stringed.play() MIDDLE_C Brass.play() MIDDLE_C Woodwind.play() MIDDLE_C *///:~

사실 tune () 방법을 변경할 필요가 없습니다. 모든 새로운 클래스는 기존 클래스와 함께 정확하게 실행됩니다. tune () 이 파일에 단독으로 저장되어 있고 Instrument 인터페이스에 다른 새로운 방법이 추가되어 있어도,tune () 는 더 이상 작성하지 않아도 정확하게 실행될 수 있습니다.
이를 통해 알 수 있듯이tune() 방법은 주위 코드가 발생하는 변화를 완전히 무시하고 여전히 정상적으로 운행한다. 이것이 바로 우리가 기대하는 다태적 특성이다.다태는 프로그래머로 하여금 변화하는 사물과 변하지 않는 사물을 분리하게 하는 중요한 기술이다.

좋은 웹페이지 즐겨찾기