[C#] 19. 객체 지향(OOP) 프로그래밍의 4대 원칙(캡슐화, 추상화, 상속, 다형성)


Study/C#  2020. 7. 28. 18:25

안녕하세요. 명월입니다.


이 글은 C#의 객체 지향(OOP) 프로그래밍의 4대 원칙(캡슐화, 추상화, 상속, 다형성)에 대한 글입니다.


객체 지향(Object-Oriented Programming)이라는 것은 프로그래밍 방식 중 하나입니다. 프로그램 방식이란, 프로그램을 개발할 때에 어떤 목적의 중심으로 개발을 하는가에 대한 방식입니다.

그 중 객체 지향은 객체(Object)를 중심으로 프로그램을 설계, 개발해 나가는 것을 말합니다.


예를 들면, "업무 계획서 작성 -> 계획 실행 -> 테스트 -> 결과 확인 -> 보고서 작성 -> 결제 -> 승인"으로 된 하나의 업무 프로세스를 생각해 봅니다.

여기서 먼저 전체 업무 단위(Controller)로 구성하고 세부적으로 계획서 데이터(Object), 테스트 데이터(Object), 결과 데이터(Object), 보고서 데이터(Object), 결제 데이터(Object)를 프로세스의 흐름에 맞게 배치하는 것입니다.

프로그램 언어로 생각하면 가장 최소 단위의 클래스로 개체(Object)를 만들고 관리하는 방식이 객체 지향이지 않을까 싶습니다.


이러한 객체 지향에는 4가지의 원칙이 있는데 이것이 캡슐화, 추상화, 상속, 다형성입니다.

이 원칙에 대해서는 부분적으로 다른 글에서 설명한 적이 있습니다.

링크 - [C#] 11. static과 접근 제한자 그리고 캡슐화

링크 - [C#] 14. 인터페이스(Interface)

링크 - [C#] 13. 추상 클래스(abstract)와 추상 함수(abstract)그리고 가상 함수(virtual)

링크 - [C#] 08. 함수(Method)와 오버로딩과 재귀 호출


이 글에서는 그러한 특성을 좀 더 자세하게 정리하겠습니다.

캡슐화

캡슐화는 클래스의 접근을 제한하는 것과 관계가 있습니다. 예를 들면 클래스의 하나의 특성에서 맴버 변수와 함수가 모두 public(모두 접근)으로 만들어지는 경우, 클래스의 고유의 객체 특성을 잃어버리기 때문에 이런 필요한 데이터 이외는 private(내부에서만 접근 가능)를 설정하는 것이 맞습니다.

using System;
using System.Collections.Generic;

namespace Example
{
  // 국어 클래스
  class Korean
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public Korean(int score)
    {
      this.score = score;
    }
  }
  // 영어 클래스
  class English
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public English(int score)
    {
      this.score = score;
    }
  }
  // 수학 클래스
  class Math
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public Math(int score)
    {
      this.score = score;
    }
  }
  // 학생 클래스
  class People
  {
    // 이름
    private String name;
    // 국어 성적
    private Korean korean;
    // 영어 성적
    private English english;
    // 수학 성적
    private Math math;
    // 생성자로 이름과 점수를 받는다.
    public People(String name, int korean, int english, int math)
    {
      this.name = name;
      this.korean = new Korean(korean);
      this.english = new English(english);
      this.math = new Math(math);
    }
  }
  // 학급 클래스
  class SchoolClass
  {
    // 학급 인원 리스트
    private List<People> peoples = new List<People>();
    // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
    public void AddPeople(String name, int korean, int english, int math)
    {
      // 학생을 추가한다.
      peoples.Add(new People(name, korean, english, math));
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 학급을 할당한다.
      SchoolClass schoolclass = new SchoolClass();
      // 학생을 임의로 추가한다.
      schoolclass.AddPeople("A", 50, 60, 70);
      schoolclass.AddPeople("B", 70, 20, 50);
      schoolclass.AddPeople("C", 60, 70, 40);
      schoolclass.AddPeople("D", 30, 80, 30);
      schoolclass.AddPeople("E", 50, 100, 50);
      schoolclass.AddPeople("F", 70, 70, 60);
      schoolclass.AddPeople("G", 90, 40, 40);
      schoolclass.AddPeople("H", 100, 100, 90);
      schoolclass.AddPeople("I", 40, 50, 10);
      schoolclass.AddPeople("J", 60, 70, 30);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 소스를 보시면 학급 클래스를 만들고, 학생 클래스, 국어, 영어, 수학을 두었습니다.


여기서 제가 캡슐화의 특성을 알기 위해서 총점과 평균을 구하는 함수를 만들어 보겠습니다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 국어 클래스
  class Korean
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public Korean(int score)
    {
      this.score = score;
    }
    // 점수 취득 함수
    public int GetScore()
    {
      return this.score;
    }
  }
  // 영어 클래스
  class English
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public English(int score)
    {
      this.score = score;
    }
    // 점수 취득 함수
    public int GetScore()
    {
      return this.score;
    }
  }
  // 수학 클래스
  class Math
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public Math(int score)
    {
      this.score = score;
    }
    // 점수 취득 함수
    public int GetScore()
    {
      return this.score;
    }
  }
  // 학생 클래스
  class People
  {
    // 이름
    private String name;
    // 국어 성적
    private Korean korean;
    // 영어 성적
    private English english;
    // 수학 성적
    private Math math;
    // 총점
    private int total;
    // 평균
    private int avg;
    // 생성자로 이름과 점수를 받는다.
    public People(String name, int korean, int english, int math)
    {
      this.name = name;
      this.korean = new Korean(korean);
      this.english = new English(english);
      this.math = new Math(math);
      this.total = korean + english + math;
      this.avg = this.total / 3;
    }
    public string GetName()
    {
      return this.name;
    }
    // 총점 취득 함수
    public int GetTotal()
    {
      return this.total;
    }
    // 평균 취득 함수
    public int GetAvg()
    {
      return this.avg;
    }
    // 석차 구하기
    public int GetRank(List<People> peoples)
    {
      // 석차
      int rank = 1;
      foreach (People p in peoples.OrderByDescending(x => x.GetTotal()))
      {
        // 같은 클래스면 continue
        if (p == this)
        {
          continue;
        }
        // 비교하는 대상이 총점이 높으면 나는 석차가 내려간다.
        if (p.GetTotal() > this.GetTotal())
        {
          rank++;
        }
      }
      // 현재 석차
      return rank;
    }
  }
  // 학급 클래스
  class SchoolClass
  {
    // 학급 인원 리스트
    private List<People> peoples = new List<People>();
    // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
    public void AddPeople(String name, int korean, int english, int math)
    {
      // 학생을 추가한다.
      peoples.Add(new People(name, korean, english, math));
    }
    // 출력 함수
    public void Print()
    {
      // 학생들의 이름과 총점, 평균, 석차를 구한다.
      foreach (People p in peoples)
      {
        // 콘솔 출력
        Console.WriteLine(p.GetName() + " total = " + p.GetTotal() + ", avg = " + p.GetAvg() + ", ranking = " + p.GetRank(peoples));
      }
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 학급을 할당한다.
      SchoolClass schoolclass = new SchoolClass();
      // 학생을 임의로 추가한다.
      schoolclass.AddPeople("A", 50, 60, 70);
      schoolclass.AddPeople("B", 70, 20, 50);
      schoolclass.AddPeople("C", 60, 70, 40);
      schoolclass.AddPeople("D", 30, 80, 30);
      schoolclass.AddPeople("E", 50, 100, 50);
      schoolclass.AddPeople("F", 70, 70, 60);
      schoolclass.AddPeople("G", 90, 40, 40);
      schoolclass.AddPeople("H", 100, 100, 90);
      schoolclass.AddPeople("I", 40, 50, 10);
      schoolclass.AddPeople("J", 60, 70, 30);
      // 출력
      schoolclass.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위에서 보면 제가 생성자에서 국어, 수학, 영어 점수를 더하고 평균을 구합니다. 그런데 여기서 제가 만약에 맴버 변수의 total과 avg의 접근 제한자를 public으로 열어버리면 어떻게 될까요?

SchoolClass 클래스에서 총점과 평균이 수정이 가능해 집니다. 이 뜻은 학생(People) 클래스의 캡슐화가 되지 않으면서 외부에서 수정이 가능해지고 다시 만약 제가 이 점수를 수정해 버리면 클래스의 데이터의 무결성이 없어지며 데이터의 신뢰도가 떨어지게 됩니다.

즉, 지금은 작은 프로젝트이지만 큰 프로젝트에서 예상하는 값이 나오지 않고 달라질 경우, 그 변수를 참조하고 수정하는 부분을 다 찾아야 하는 사태가 벌어지게 됩니다.


캡슐화는 개발자들끼리 어느 정도 약속한 룰이 있는데... 맴버 변수는 무조건 private, 함수는 필요한 부분만 public을 열고 private로 설정, 확장을 하는데 필요한 클래스라면 protected로 설정을 하게 됩니다.

상속

상속은 비슷한 객체끼리 부모 클래스와 인터페이스를 정의하여 공통화한 다음 상속받아서 객체를 좀 더 다루기 쉽게 하는 특징입니다. 위 예제만 보더라도 객체 지향으로 작성을 하면 일단 소스가 길어 보입니다.

무언가 객체 지향이 확실히 가독성이 좋아 보이는데도.. 무언가 부족해 보입니다. 이제 비슷한 객체끼리는 묶어 버립니다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 과목 인터페이스
  interface ISubject
  {
    int GetScore();
  }
  // 과목 추상 클래스
  abstract class AbstractSubject : ISubject
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public AbstractSubject(int score)
    {
      this.score = score;
    }
    // 점수 취득 함수
    public int GetScore()
    {
      return this.score;
    }
  }

  // 국어 클래스
  class Korean : AbstractSubject
  {
    // 생성자
    public Korean(int score) : base(score) { }
  }
  // 영어 클래스
  class English : AbstractSubject
  {
    // 생성자
    public English(int score) : base(score) { }
  }
  // 수학 클래스
  class Math : AbstractSubject
  {
    // 생성자
    public Math(int score) : base(score) { }
  }
  // 학생 클래스
  class People
  {
    // 이름
    private String name;
    // 0 - 국어, 1 - 영어, 2 - 수학
    private List<ISubject> subjects = new List<ISubject>();
    // 총점
    private int total;
    // 평균
    private int avg;
    // 생성자로 이름과 점수를 받는다.
    public People(String name, int korean, int english, int math)
    {
      this.name = name;
      this.subjects.Add(new Korean(korean));
      this.subjects.Add(new English(english));
      this.subjects.Add(new Math(math));
      // 총점 구하기
      this.total = this.subjects.Sum(x => x.GetScore());
      // 평균 구하기
      this.avg = this.total / 3;
    }
    public string GetName()
    {
      return this.name;
    }
    // 총점 취득 함수
    public int GetTotal()
    {
      return this.total;
    }
    // 평균 취득 함수
    public int GetAvg()
    {
      return this.avg;
    }
    // 석차 구하기
    public int GetRank(List<People> peoples)
    {
      // 석차
      int rank = 1;
      foreach (People p in peoples.OrderByDescending(x => x.GetTotal()))
      {
        // 같은 클래스면 continue
        if (p == this)
        {
          continue;
        }
        // 비교하는 대상이 총점이 높으면 나는 석차가 내려간다.
        if (p.GetTotal() > this.GetTotal())
        {
          rank++;
        }
      }
      // 현재 석차
      return rank;
    }
  }
  // 학급 클래스
  class SchoolClass
  {
    // 학급 인원 리스트
    private List<People> peoples = new List<People>();
    // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
    public void AddPeople(String name, int korean, int english, int math)
    {
      // 학생을 추가한다.
      peoples.Add(new People(name, korean, english, math));
    }
    // 출력 함수
    public void Print()
    {
      // 학생들의 이름과 총점, 평균, 석차를 구한다.
      foreach (People p in peoples)
      {
        // 콘솔 출력
        Console.WriteLine(p.GetName() + " total = " + p.GetTotal() + ", avg = " + p.GetAvg() + ", ranking = " + p.GetRank(peoples));
      }
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 학급을 할당한다.
      SchoolClass schoolclass = new SchoolClass();
      // 학생을 임의로 추가한다.
      schoolclass.AddPeople("A", 50, 60, 70);
      schoolclass.AddPeople("B", 70, 20, 50);
      schoolclass.AddPeople("C", 60, 70, 40);
      schoolclass.AddPeople("D", 30, 80, 30);
      schoolclass.AddPeople("E", 50, 100, 50);
      schoolclass.AddPeople("F", 70, 70, 60);
      schoolclass.AddPeople("G", 90, 40, 40);
      schoolclass.AddPeople("H", 100, 100, 90);
      schoolclass.AddPeople("I", 40, 50, 10);
      schoolclass.AddPeople("J", 60, 70, 30);
      // 출력
      schoolclass.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 캡슐화에서 소개한 소스와 다른 점은 인터페이스(Subject)를 만들고, 공통되는 함수(GetScore)를 추상 클래스(AbstractSubject)에 두었습니다.

학생(People) 클래스에도 성적을 각각의 맴버 변수가 아닌 List로 관리하여.. 국어, 영어, 수학 뿐 아니라 과목이 추가가 되면 확장이 되겠끔 수정했습니다.

추상화

위 상속할 때 이미 추상화를 했지만 여기서는 메서드 추상화와 재정의(Override)에 대한 설명으로 이어가겠습니다.

여기서 우리는 국어, 영어, 수학이 100점을 기준으로 했지만, 요번의 교육 개편으로 국어는 예전 100에서 120으로, 영어는 100에서 80점으로, 수학은 그대로 100으로 과목의 점수 배점을 바꾸어야 합니다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 과목 인터페이스
  interface ISubject
  {
    int GetScore();
  }
  // 과목 추상 클래스
  abstract class AbstractSubject : ISubject
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public AbstractSubject(int score)
    {
      this.score = score;
    }
    // 점수 취득 함수
    public virtual int GetScore()
    {
      return this.score;
    }
  }

  // 국어 클래스
  class Korean : AbstractSubject
  {
    // 생성자
    public Korean(int score) : base(score) { }
    // 점수 재정의
    public override int GetScore()
    {
      return (int)((float)base.GetScore() * 1.2f);
    }
  }
  // 영어 클래스
  class English : AbstractSubject
  {
    // 생성자
    public English(int score) : base(score) { }
    // 점수 재정의
    public override int GetScore()
    {
      return (int)((float)base.GetScore() * 0.8f);
    }
  }
  // 수학 클래스
  class Math : AbstractSubject
  {
    // 생성자
    public Math(int score) : base(score) { }
  }
  // 학생 클래스
  class People
  {
    // 이름
    private String name;
    // 0 - 국어, 1 - 영어, 2 - 수학
    private List<ISubject> subjects = new List<ISubject>();
    // 총점
    private int total;
    // 평균
    private int avg;
    // 생성자로 이름과 점수를 받는다.
    public People(String name, int korean, int english, int math)
    {
      this.name = name;
      this.subjects.Add(new Korean(korean));
      this.subjects.Add(new English(english));
      this.subjects.Add(new Math(math));
      // 총점 구하기
      this.total = this.subjects.Sum(x => x.GetScore());
      // 평균 구하기
      this.avg = this.total / 3;
    }
    public string GetName()
    {
      return this.name;
    }
    // 총점 취득 함수
    public int GetTotal()
    {
      return this.total;
    }
    // 평균 취득 함수
    public int GetAvg()
    {
      return this.avg;
    }
    // 석차 구하기
    public int GetRank(List<People> peoples)
    {
      // 석차
      int rank = 1;
      foreach (People p in peoples.OrderByDescending(x => x.GetTotal()))
      {
        // 같은 클래스면 continue
        if (p == this)
        {
          continue;
        }
        // 비교하는 대상이 총점이 높으면 나는 석차가 내려간다.
        if (p.GetTotal() > this.GetTotal())
        {
          rank++;
        }
      }
      // 현재 석차
      return rank;
    }
  }
  // 학급 클래스
  class SchoolClass
  {
    // 학급 인원 리스트
    private List<People> peoples = new List<People>();
    // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
    public void AddPeople(String name, int korean, int english, int math)
    {
      // 학생을 추가한다.
      peoples.Add(new People(name, korean, english, math));
    }
    // 출력 함수
    public void Print()
    {
      // 학생들의 이름과 총점, 평균, 석차를 구한다.
      foreach (People p in peoples)
      {
        // 콘솔 출력
        Console.WriteLine(p.GetName() + " total = " + p.GetTotal() + ", avg = " + p.GetAvg() + ", ranking = " + p.GetRank(peoples));
      }
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 학급을 할당한다.
      SchoolClass schoolclass = new SchoolClass();
      // 학생을 임의로 추가한다.
      schoolclass.AddPeople("A", 50, 60, 70);
      schoolclass.AddPeople("B", 70, 20, 50);
      schoolclass.AddPeople("C", 60, 70, 40);
      schoolclass.AddPeople("D", 30, 80, 30);
      schoolclass.AddPeople("E", 50, 100, 50);
      schoolclass.AddPeople("F", 70, 70, 60);
      schoolclass.AddPeople("G", 90, 40, 40);
      schoolclass.AddPeople("H", 100, 100, 90);
      schoolclass.AddPeople("I", 40, 50, 10);
      schoolclass.AddPeople("J", 60, 70, 30);
      // 출력
      schoolclass.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

GetScore 함수를 virtual로 설정한 다음 국어랑 영어에서 재정의를 했습니다.

단순히 재정의하는 것만으로 점수 배점이 바뀌니 총점과 평균, 석차가 바뀌네요... 객체 지향 형식(OOP)을 만들면 이렇게 객체의 확장성과 수정이 매우 편해 집니다.

다형성

다형성은 같은 메소드에 파라미터가 다른 형태의 구조를 만드는 것입니다.


이번에는 과목을 국어, 영어, 수학 뿐이 아니라 선택 과목을 추가하겠습니다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // 과목 인터페이스
  interface ISubject
  {
    int GetScore();
  }
  // 과목 추상 클래스
  abstract class AbstractSubject : ISubject
  {
    // 점수
    private int score;
    // 생성자로 점수를 받는다.
    public AbstractSubject(int score)
    {
      this.score = score;
    }
    // 점수 취득 함수
    public virtual int GetScore()
    {
      return this.score;
    }
  }

  // 국어 클래스
  class Korean : AbstractSubject
  {
    // 생성자
    public Korean(int score) : base(score) { }
    // 점수 재정의
    public override int GetScore()
    {
      return (int)((float)base.GetScore() * 1.2f);
    }
  }
  // 영어 클래스
  class English : AbstractSubject
  {
    // 생성자
    public English(int score) : base(score) { }
    // 점수 재정의
    public override int GetScore()
    {
      return (int)((float)base.GetScore() * 0.8f);
    }
  }
  // 수학 클래스
  class Math : AbstractSubject
  {
    // 생성자
    public Math(int score) : base(score) { }
  }
  // 선택 클래스
  class Select : AbstractSubject
  {
    // 생성자
    public Select(int score) : base(score) { }
  }
  // 학생 클래스
  class People
  {
    // 이름
    private String name;
    // 0 - 국어, 1 - 영어, 2 - 수학
    private List<ISubject> subjects = new List<ISubject>();
    // 총점
    private int total;
    // 평균
    private int avg;
    // 생성자로 이름과 점수를 받는다.
    public People(String name, int korean, int english, int math)
    {
      this.name = name;
      this.subjects.Add(new Korean(korean));
      this.subjects.Add(new English(english));
      this.subjects.Add(new Math(math));
      // 총점 구하기
      this.total = this.subjects.Sum(x => x.GetScore());
      // 평균 구하기
      this.avg = this.total / this.subjects.Count;
    }
    // 생성자로 이름과 점수를 받는다. (다형성)
    public People(String name, int korean, int english, int math, int select)
    {
      this.name = name;
      this.subjects.Add(new Korean(korean));
      this.subjects.Add(new English(english));
      this.subjects.Add(new Math(math));
      // 선택 과목 추가
      this.subjects.Add(new Select(select));
      // 총점 구하기
      this.total = this.subjects.Sum(x => x.GetScore());
      // 평균 구하기
      this.avg = this.total / this.subjects.Count;
    }
    public string GetName()
    {
      return this.name;
    }
    // 총점 취득 함수
    public int GetTotal()
    {
      return this.total;
    }
    // 평균 취득 함수
    public int GetAvg()
    {
      return this.avg;
    }
    // 석차 구하기
    public int GetRank(List<People> peoples)
    {
      // 석차
      int rank = 1;
      foreach (People p in peoples.OrderByDescending(x => x.GetTotal()))
      {
        // 같은 클래스면 continue
        if (p == this)
        {
          continue;
        }
        // 비교하는 대상이 평균이 높으면 나는 석차가 내려간다.
        if (p.GetAvg() > this.GetAvg())
        {
          rank++;
        }
      }
      // 현재 석차
      return rank;
    }
  }
  // 학급 클래스
  class SchoolClass
  {
    // 학급 인원 리스트
    private List<People> peoples = new List<People>();
    // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
    public void AddPeople(String name, int korean, int english, int math)
    {
      // 학생을 추가한다.
      peoples.Add(new People(name, korean, english, math));
    }
    // 다형성으로 같은 함수 이름으로 학생 추가 함수, 이름과 국어, 영어, 수학, 선택 과목 성적을 받는다.
    public void AddPeople(String name, int korean, int english, int math, int select)
    {
      // 학생을 추가한다.
      peoples.Add(new People(name, korean, english, math, select));
    }
    // 출력 함수
    public void Print()
    {
      // 학생들의 이름과 총점, 평균, 석차를 구한다.
      foreach (People p in peoples)
      {
        // 콘솔 출력
        Console.WriteLine(p.GetName() + " total = " + p.GetTotal() + ", avg = " + p.GetAvg() + ", ranking = " + p.GetRank(peoples));
      }
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 학급을 할당한다.
      SchoolClass schoolclass = new SchoolClass();
      // 학생을 임의로 추가한다.
      schoolclass.AddPeople("A", 50, 60, 70);
      schoolclass.AddPeople("B", 70, 20, 50);
      schoolclass.AddPeople("C", 60, 70, 40);
      schoolclass.AddPeople("D", 30, 80, 30);
      schoolclass.AddPeople("E", 50, 100, 50);
      schoolclass.AddPeople("F", 70, 70, 60);
      schoolclass.AddPeople("G", 90, 40, 40);
      schoolclass.AddPeople("H", 100, 100, 90);
      schoolclass.AddPeople("I", 40, 50, 10);
      schoolclass.AddPeople("J", 60, 70, 30);
      // 선택 과목 점수가 있는 학생
      schoolclass.AddPeople("K", 60, 70, 30, 70);
      schoolclass.AddPeople("L", 60, 70, 30, 100);

      // 출력
      schoolclass.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

선택 과목에 대한 클래스를 먼저 추가했습니다.

그리고 학급(SchoolClass) 클래스의 AddPeople 함수의 다형성으로 선택 과목의 점수도 넣는 함수를 만들었습니다.

그리고 학생(People) 클래스의 생성자도 추가했습니다. 석차는 총점으로는 값의 오류가 있기 때문에(4과목의 총점이 당연히 높기 때문에) 평균으로 수정했습니다.


마지막으로 K와 L의 학생은 선택 과목 점수를 추가했습니다.

이렇게 함수 명을 맞추어서 개발할 필요는 없지만 함수명과 변수명은 클래스의 가독성을 위해 매우 중요합니다. 그렇기 때문에 이런 다형성을 통해서 함수명과 변수명을 이용해서 의미를 정확하게 객체의 의미를 정확하게 만드는 것입니다.


객체 지향 프로그래밍을 보면 객체 별로 데이터를 구분하기 때문에 꽤 가독성이 올라갑니다. 그리고 유지 보수를 하는데 있어서도 사양이 추가되는 것에 따라 크게 수정도 발생하지 않습니다.

최근에는 프로그램이 데이터 중심이고 빅 데이터로 돌아가기 때문에 객체 지향의 개발 방식이 매우 중요합니다.


그러나 이런 만능으로 보이는 객체 지향도 단점이 있습니다. 위 예제는 모두 데이터 형식으로 설명했기 때문에 꽤나 효율적으로 보이지만 모든 프로그램이 데이터 형식으로만 돌아가는 건 아닙니다.

예를 들면 로드 밸런싱 프로그램을 만든다거나 화상 채팅 툴을 만들게 되면, 이건 데이터 중심이 아닌 프로세스의 행위 중심이기 때문에 오히려 객체 지향으로 작성을 하면 더 복잡해집니다.


가까운 예로, MVC로 WebSite를 만들 때, 데이터 베이스에서 데이터를 받고 클라이언트(브라우져)에 데이터를 넘겨주는 형식은 분명 데이터 중심이고 객체 지향이 좋습니다.

그러나 Controller의 경우는 맴버 변수 없이 오로지 클라이언트에서 접속을 받고 디비에 접속에서 데이터를 받고 다시 클라이언트에 넘겨주는 흐름 중심이기 때문에 오히려 이런 부분은 객체 지향이 좋지 않습니다.

그래서 MVC 프로젝트를 하면 디비 데이터의 객체 Model 클래스나 폼 데이터의 Bean 클래스는 잘 정리가 되어 있는데 Controller의 경우는 Action, Controller마다 클래스와 함수를 생성해 버리기 때문에 의미없이 클래스와 함수만 늘어나는 형식이 되어버립니다.

그래서 MVC 프로젝트를 보면 Model이나 Bean 클래스보다 Controller 클래스만 왕창 보이게 됩니다. 뭐 개발자의 능력에 따라 binding을 어떻게 하느냐에 달라지기는 하지만.. 기본적으로 그렇습니다.


여기까지 C#의 객체 지향(OOP) 프로그래밍의 4대 원칙(캡슐화, 추상화, 상속, 다형성)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.