ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프로그래밍 면접 이렇게 준비한다. - 4. 객체지향
    프로그래밍/방법론 2014. 7. 25. 18:14
    반응형

      1. 기본원리

     - 실무 개발자들이 OOP(Object-Oriented Programming) 언어를 폭넓게 도입하면서부터 연구가 더욱 활발해졌다.

     

      2. 클래스와 객체

     - 객체지향성에 대한 정의는 클래스와 객체가 그 중심에 서 있음은 분명하다.

     - 클래스(Class)는 속성(attribute, 성질property이나 상태state라고도 부름)과 행동(actions, 능력capability 또는 메소드method라고도 부름)을 가진 무언가를 추상적으로 정의한 것이다.

     - 객체(Object)는 다른 객체 인스턴스와는 다른 별도의 상태를 가지고 있는 어떤 클래스의 특정 인스턴스를 뜻한다. 직교좌표계에서 어떤 점의 x값과 y값을 나타내는 한 쌍의 정수가 들어있는 Point라는 클래스는 다음과 같이 정의할 수 있다.

    public class Point{

      private int x;

      private int y;

      public Point(int x, int y){

        this.x = x; // this란, 메개변수와 객체의 변수이름이 같을 때, 객체 자체를 가르킴

        this.y = y;

      }

      public Point(Point other){

        x = other.getX();

        y = other.getY();

      }

      public int getX(){ return x; }

      public int getY(){ return y; }

      public Point relativeTo(int dx, int dy){

        return new Point(x + dx, y + dy);

      }

      public String toString(){

        StringBuffer b = new StringBuffer();

        b.append('(');

        b.append(x);

        b.append(',');

        b.append(y);

        b.append(')');

        return b.toString();

      }

    }

     - 어떤 점을 표현하려면 적당한 값을 가지고 Point 클래스의 인스턴스를 만들기만 하면 된다.

    Point p1 = new Point(5, 10);

    Point p2 = p1.relativeTo(-5, 5);

    System.out.println(p2.toString()); //(0,15) 출력

     - 이 간단한 예에도 객체지향 프로그래밍의 중요한 원칙 가운데 하나인 캡슐화(encapsulation, 구현과 관련된 자세한 내용을 숨기는 것)가 포함되어 있다.

     

      3. 상속과 다형성

     - 상속과 다형성은 밀접하게 연관되어 있다.

     - 상속(inheritance)은 어떤 클래스에서 더 특화된 버전의 클래스를 위한 행동을 제공할 수 있게 해준다.

       자바에서는 B가 A를 확장한다라는 표현으로, B라는 클래스가 A라는 클래스로부터 상속을 받을 때를 의미한다. (A는 B의 부모클래스, B는 A의 서브클래스가 된다)

       A 클래스에서 정의한 행동은 모두 B클래스의 행동이 되며, 일부를 변형할 수도 있다. 그리고 A클래스의 인스턴스가 필요한 곳이라면 어디에든 B 클래스를 사용해도 된다.

      - 다형성(polymorphism)은 한 행동을 여러 방법으로 구현하고 상황에 따라 적당한 구현을 선택해서 쓸 수 있도록 해 주는 기능을 제공한다. 예를 들어, 한 클래스에서 서로 다른 매개 변수를 써서 한 메소 드를 두 가지 버전으로 정의할 수 있다.

       아니면, 부모클래스와 스브클래스에서 같은 메소드를 다르게 정의하여 서브클래스의 인스턴스에 대해서는 서브클래스에서 정의한 메소드가 부모 클래스에서 정의한 메소드를 오버라이드(override)하도록 할 수도 있다. 실행될 메소드는 코드가 컴파일될 때, 또는 애플리케이션이 실행될 때 선택한다.

     - 상속과 다형성의 대표적인 예로 벡터 기반으로 그림을 그리는 애플리케이션에서 서로 다른 도형을 나타내는 도형 라이브러리를 생각할 수 있다. 이 계층 구조의 맨 위에는 모든 도형들이 공통적으로 가지는 것들을 정의하는 Shape 클래스가 있다.

    Public abstract class Shape{

      protected Point center;

      protected Shape(Point center){

        this.center = center;

      }

      public Point get Center(){

        return center; // Point는 변형이 불가능하다. 오직 get만 가능.

      }

      public abstract Rectangle getBounds();

      public abstract void draw(Graphics g);

    }

     - 틀화된 도형을 나타내는 Rectangle(정사각형)과 Ellipse(타원) 서브 클래스를 정의해 보자.

    public class Rectangle extends Shape{

      private int h;

      private int w;

      public Rectangle(Point center, int w, int h){

        super(center); // super()란, 부모클래스의 생성자 함수를 가르킴 (Shape calss의 Shape(Point center) 함수)

        this.w = w;

        this.h = h;

      }

      public Rectangle getBounds(){

        return this;

      }

      public int getHeight(){return h;}

      public int getWidth(){return w;}

      public void draw(Graphics g){

        // 직사각형을 그리는 코드

      }

      public class Ellipse extends Shape{

        private int a;

        private int b;

        public Ellipse(Point center, int a, int b){

          private int a;

          private int b;

          public Ellipse(Point center, int a, int b){

            super(center);

            this.a = a;

            this.b = b;

          }

          public Rectangle getBounds(){

            return new Rectangle(center, a*2, b*2);

          }

        }

        public int getSemiMajorAxis(){return a;}

        public int getSemiMinorAxis(){return b;}

        Public void draw(Graphics g){

          ... // 타원을 그리는 코드

        }

      }

    }

     - 원한다면 Rectangle과 Ellipse를 더 특화시켜서 각각 정사각형을 나타내는 Square나 원을 나타내는 Circle 같은 서브클래스를 만들 수도 있다.

     - 라이브러리에서 여러 도형을 정의해도 애플리케이션에서 도형을 화면에 그리는 코드는 매우 간단하다.

    void paintShapes(Graphics g, List<Shape> shapes){

      for(Shape s : shapes){

        s.draw(g);

      }

    }

     

      4. 객체지향 프로그래밍 문제

     1) 인터페이스와 추상 클래스

     - 객체지향 프로그래밍에서 인터네이스와 추상 클래스의 차이점을 설명하라.

     - 인터페이스(interface)에서는 클래스와 별도로 일련의 연관된 메소드를 선언한다.

     - 추상 클래스(abstract class)는 메소드를 선언하기는 하지만 모든 메소드를 정의하지는 않는 불완전하게 정의된 클래스이다.

    - 개념 면에서 보면, 인터페이스는 클래스 계층 구조와는 독립적으로 애플리케이션 프로그래밍 인터페이스(API, Application Programming Interface)를 정의하는 같은 역할을 한다.

      인터페이스는 특히 단일 상속만을 지원하는 (하나의 클래스에서만 상속) 언어에서 매우 중요하다. 어떤 클래스에 특정 인터페이스에서 정한 모든 메소드가 들어있다면 그 클래스는 해당 인터페이스를 구현한다고 표현한다.

      인터페이스는 데이터 멤버 및 메소드 정의가 들어가 있지 않은 추상클래스하고 거의 똑같다. 실제 C++에서는 데이터 멤버도 없고 순수한 가상 함수만 들어있는 클래스를 선언하면 된다.

    class StatusCallback{

      public:

      virtual void updateStatus(int oState, int nState) = 0;

    }

    class Myclass : SomeOtherClass, StatusCallback{

      public:

        void updateStatus(int oState, int nState){

          if(nState > oState){

            .... // 작업처리

          }

          ... // 클래스 나머지 부분

        }

    }

     - 자바에서는 interface 키워드를 써서 인터페이스를 정의한다.

    public interface StatusCallback{

      void updateStatus(int oState, inte nState);

    }

     - 그리고 클래스에서 인터페이스를 구현할 때는 다음과 같이 한다.

    public class Myclass inplements StatusCallback{

      public void updateStates(int oState, int nState){

        if(nState>oState){

          //작업처리

        }

      }

    ... // 클래스 나머지 부분

    }

     - 인터페이스와 달리 추상클래스는 그 자체가 클래스다. 데이터 멤버도 들어갈 수 있고 다른 클래스의 서브클래스로 만들 수도 있다. 하지만 (추상이 아닌) 구상 클래스와 달리 행동 중 일부는 정의하지 않고 서브클래스에서 정의해서 쓰도록 남겨둔다. 이런 이유 때문에 추상클래스의 인스턴스는 만들 수 없다. 그 클래스를 상속하는 구상 서브클래스의 인스턴스를 생성해서 써야만 한다.

     

     

    인터페이스 

    추상 클래스 

     메소드

    O (선언) 

    O (선언&정의) 

    데이터 멤버

     상속

    단일상속 

    단일/다중상속 

     

     - 예를 들어, 인터페이스와 추상 클래스를 모두 지원하는 언어에서는 다음과 같이 사용한다.

    public interface XMLReader{

      public XMLObject fromString(String str);

      public XMLObject fromReader(Reader in);

    }

     - 다음과 같은 식으로 기본 구현을 제공할 수 있다.

    public abstract class XMLReaderImpl implements XMLReader{

      public XMLObject fromString(String str){

        return fromReader(new StringReader(str));

      }

      public abstract XMLObject fromReader(Reader in);

    }

     - XMLReader를 구현하고자 하는 프로그래머 입장에서는 XMOLReaderImpl의 서브클래스를 만들어서 쓰면 두 메소드를 모두 구현하지 않고 하나만 구현해서 써도 되기 때문에 편리하다.

     2) 다중 상속

     Q) C#과 JAVA에서 클래스의 다중 상속을 허용하지 않는 이유는?

     - C++에서는 한 클래스에서 (직간접적으로) 하나 이상의 클래스를 상속할 수 있으며, 이를 다중 상속(multiple ingeritance)이라 부른다. 하지만 C#과 자바에서는 단일 상속(single inheritance, 한 클래스의 부모 클래스가 단 하나뿐임)만 허용한다.

     - 한 애플리케이션에서 서로 다른 클래스 프레임워크를 사용한다든가 하는 경우에는 두 개의 서로 다른 클래스 계층 구조를 결합한 클래스를 만들어 써야 하는 경우가 있는데, 이런 경우에 다중 상속을 활용할 수 있다. 예를 들어, 두 프레임워크에서 예외 상황을 서로 다른 베이스 클래스로 정의해 놓았다면 다중 상속을 써서 어느 프레임워크에서든 사용할 수 있는 예외 상황 클래스를 만들 수도 있다.

     - 하지만, "애매함" 때문이다. 문제는 한 클래스가 두 개의 서로 다른 클래스를 상속하는데, 그 두 부모 클래스가 한 클래스로부터 파생된 클래스인 경우를 들 수 있다.

    class A{

      protected:

      bool flag;

    };

    class B : public A{};

    class C : public A{};

    class D : public B, public C{

      public:

        boid setFlag(bool nflag){

          void setFlag(bool nflag){

            flag = nflag; // 애매한 부분

          }

        }

    }

     - 이 예를 보면 flag 데이터 멤버는 A클래스에서 정의되는데, D클래스는 B와 C라는 클래스의 서브클래스이며, B와 C는 둘 다 A의 서브클래스이므로, D의 클래스 계층 구조를 보면 A가 두 번 등장하게 되어 flag가 두 개 만들어지는 셈이 된다. 따라서 D에서 flag에 대한 레퍼런스가 모호하다는 메시지를 보낸다.

     - 해결책으로 B와 C를 가상 베이스 클래스로 선언하여 클래스 계층 구조에 A의 사본이 하나만 들어가도록 하는 방법이 있다.

      또 다른 해결책으로 다음과 같이 레퍼런스를 명시적으로 표시하는 방법이 있다.

      B::flag = nflag;

     

    반응형
Designed by Tistory.