객체지향 프로그래밍이란

 

학생들이 교실청소를 분업해서 진행해야 하는 상황 가정
진행해야 하는 작업은 총 3가지 (바닥쓸기, 창문닦이, 칠판닦이)
아래로 내려갈수록 점점 코드가 발전하는(체계화되는) 방식

절차지향 프로그래밍

  1. 학생 한명 한명에게 명령어를 직접 쳐서 바닥쓸기, 창문닦이, 칠판닦이를 시킴
    • 같은 작업인데도 지정하는 학생이 달라질 때 마다 코드를 똑같이 다시 쳐야하기 때문에 중복이 심함
  2. 반복문으로 중복코드를 조금 없앰
  3. 학생을 인자로 받을 수 있는 함수에 2에서 활용한 반복문까지 써서 중복 코드 줄임
    • 여전히 중복이 많음
    • 여기 3번까지가 절차지향 프로그래밍 에서 가능. 하지만 청소할 곳의 범위, 작업이 복잡해지면 프로그래밍 하기 점점 어려워짐. 이제부턴 객체지향 프로그래밍을 해보자

객체지향 프로그래밍

클래스

  1. 클래스 만들어서 진행
    • 클래스 선언
     public class 창문닦이{
         private ClassRoom 담당_교실;
         private int 행주_깨끗함 = 100;
         private double 세제_남은리터 = 10.0;
    
         public 창문닦이(ClassRoom 교실){
             담당_교실 = 교실;
         }
    
         public void 참문_닦기(){
             while (!담당_교실.창문_깨끗함 ()){
                 if (행주_깨끗함 <=10 )
                     빨아오기(행주_깨끗함);
                 if (세제_남은리터 <= 1)
                     리필해오기(세제_남은리터);
                 담당_교실.창문_닦기(행주_깨끗함, 세제_남은리터);
             }
         }
     }
    
     public class 칠판닦이{
         private ClassRoom 담당_교실;
         private int 지우개_깨끗함 = 0;
    
         public 칠판닦이(ClassRoom 교실){
             담당_교실 = 교실;
         }
    
         public void 칠판_닦기(){
             while (!담당_교실.칠판_깨끗함 ()){
                 if (지우개_깨끗함 <=10 )
                     털기(지우개_깨끗함);
             }
         }
     }
    
     public class 바닥쓸이{
         private ClassRoom 담당_교실;
         private double 쓰레받기_채움 = 0;
    
         public 바닥쓸이(ClassRoom 교실){
             담당_교실 = 교실;
         }
    
         public void 바닥_쓸기(){
             while (!담당교실.바닥_깨끗함 ()){
                 if (쓰레받기_채움 >= 0.9)
                     비우기(쓰레받기_채움);
                     담당_교실.바닥_쓸기(쓰레받기_채움);
             }
         }
     }
    
     창문닦이 영수 = new 창문닦이(우리학교._101호);
     영수.창문_닦기();
    
     창문닦이 혁순 = new 창문닦이(우리학교._102호);
     혁순.창문_닦기();
    
     바닥쓸이 철권 = new 바닥쓸이(우리학교._101호);
     철권.바닥_쓸기();
    
     칠판닦이 변수 = new 칠판닦이(우리학교._101호);
     변수.칠판_닦기();
    
     ArrayList<칠판닦이> 칠판닦이들 = new ArrayList<>();
     칠판닦이들.add(new 칠판닦이(우리학교._102호));
     칠판닦이들.add(new 칠판닦이(우리학교._103호));
     칠판닦이들.add(new 칠판닦이(우리학교._104호));
     칠판닦이들.add(new 칠판닦이(우리학교._105호));
    
     for (칠판닦이 닦이: 칠판닦이들) {
         닦이.칠판_닦기();
     }
    
    • 은닉성(private 있는 이유)
      • 객체들의 변수들을 외부에서 접근하면 예기치 못한 에러가 발생할 수 있음
        • 따라서 외부가 사용가능한 메소드나 속성을 public으로 제공하고, 중요한 속성들은 private으로 감춰둠 ⇒ 은닉성
      • 리모콘을 통해 TV를 조작할 수 있지만 내부에서 어떻게 작동되서 TV가 조작되는지는 우리는 모름
    • 각 학생들은 모두 역할만 다를 뿐 다 함께 본질은 청소하는건데 위에서 보면 다른 클래스들의 청소 함수는 for문을 통해 한번에 처리가 불가능함

인터페이스

  1. 한 명령어로 모두에게 청소 하게끔 하도록 인터페이스 사용

     public interface 청소담당{
     	public void 청소();
     	public void 교실_이동 (ClassRoom 교실);
     }  // 인터페이스에서 무슨 역할을 할지 구현하지는 않음
        
     public class 창문닦이_v2 implements 청소담당{
     	private ClassRoom 담당_교실;
     	private int 행주_깨끗함 = 100;
     	private double 사용안한_세제= 10.0;
        
     	public 창문닦이_v2 (ClassRoom 교실){
     		담당_교실 = 교실;
     	}
        
     	public void 창문_닦기(){
     		while (!담당_교실.창문_깨끗함 ()) {
     			if (행주_깨끗함 <= 10)
     				빨아오기(행주_깨끗함);
     			if (윈덱스_리터 <= 1)
     				리필해오기(윈덱스_리터);
     			담당_교실.창문_닦기(행주_깨끗함, 윈덱스_리터);
     		}
     	}
        
     @Override
     public void 청소(){
     		창문_닦기();
     	}
        
     @Override
     public void 교실_이동(ClassRoom 교실) {
     		담당_교실 = 교실;
     	}
     }
    
    
     창문닦이_v2 영수 = new 창문닦이_v2(우리학교._101호);
     바닥쓸이_v2 철권 = new 바닥쓸이_v2(우리학교._101호);
     칠판닦이_v2 변수 = new 칠판닦이_v2(우리학교._101호);
    
     영수.청소();
     철권.청소();
     변수.청소();
    
    • Override를 통해 반드시 청소, 교실이동 함수를 재정의 해줘야함. 이를통해 청소 함수만 호출하면 각 클래스들에서 본인 클래스에 맞는 청소 함수를 실행하게 됨.
     ArrayList<청소담당> 청소담당들 = new ArrayList<>();
    
     청소담당들.add(new 칠판닦이_v2(우리학교._102호));
     청소담당들.add(new 칠판닦이_v2(우리학교._103호));
     청소담당들.add(new 바닦쓸이_v2(우리학교._104호));
     청소담당들.add(new 칠판닦이_v2(우리학교._105호));
    
     for (청소담당 담당: 청소담당들) {
         담당.청소();
     }
    
    • 주목할 부분
      • ArrayList의 타입이 청소담당으로 되어있는것
      • 다른 클래스들의 청소관련 함수를 청소 메서드로 호출하는 것
  2. 객체지향의 강력함 등장 → 클래스들로 또 다른 클래스 조립하기

    • 팀 단위로 청소당번을 움직여보자 (학생단위에서 팀 단위가 되어서 훨씬 관리하기 수월해짐)

        public class 교실청소팀{
        	protected ClassRoom 담당_교실;
              
        	private 창문닦이_v2 창문닦이_1, 창문닦이_2;
        	private 바닥쓸이_v2 바닥쓸이_1, 바닥쓸이_2, 바닥쓸이_3;
        	private 칠판닦이_v2 칠판닦이_1;
              
        	private 청소담당[] 팀원들 = {
        		창문닦이_1, 창문닦이_2,
        		바닥쓸이_1, 바닥쓸이_2, 바닥쓸이_3,
        		칠판닦이_1
        	};
              
        	public 교실청소팀 (ClassRoom 교실){
        		담당_교실 = 교실;
        		창문닦이_1 = new 창문닦이_v2(교실); 		
                창문닦이_2 = new 창문닦이_v2(교실);
              
        		바닥쓸이_1 = new 바닥쓸이_v2(교실); 		
                바닥쓸이_2 = new 바닥쓸이_v2(교실); 		
                바닥쓸이_3 = new 바닥쓸이_v2(교실);
              
        		칠판닦이_1 = new 칠판닦이_v2(교실);
        	}
              
        	public void 교실청소 (){		 
                // 팀원들 안에있는 클래스들이 모두 청소담당
                // 인터페이스를 implements 했으므로 
                // 청소 메소드만으로 청소 명령 가능
        		for (청소담당 팀원: 팀원들){
        			팀원.청소();
        		}
        	}
              
        	public boolean 청소완료(){
        		return 담당_교실.창문_깨끗함() && 담당_교실.바닥_깨끗함() && 담당_교실.칠판_깨끗함();
        	}
              
        	public void 교실_이동 (ClassRoom 교실){
        		for (청소담당 팀원: 팀원들){
        			팀원.교실_이동(교실);
        		}
        	}
        }
      
    • 문제 발생! 과학실은 특별한 도구들이 있어서 특별 지식을 가지고 있는 학생들이 필요함.

상속

  1. 상속 개념 발동!
    • 과학실 청소팀이라는 클래스를 통째로 새로 만들수도 있겠지만, 기존에 만들어둔 교실청소팀 클래스의 거의 모든 속성과 메소드가 같다면 중복 발생
      • 즉 과학실 청소팀을 만들긴 할건데 기존 교실청소팀을 상속해서 만들면 중복을 줄일 수 있음
      public class 과학실청소팀_v2 extends 교실청소팀{
          실험기구닦이 실험기구닦이_1;		
          // 변수가 이것밖에 없는것처럼 보이지만 
          // 상속했으므로 교실청소팀의 모든 변수 다 가져온 상태임
      
          public 과학실청소팀_v2(ClassRoom 교실){
              super(교실);		// 메모리에 교실청소팀 클래스도 인스턴스화함
              실험기구닦이_1 = new 실험기구닦이(교실);
          }
      
          @Override
          public void 교실청소(){
              super.교실청소();			
              실험기구닦이_1.청소();	// 얘도 부모의 list 변수에 add 해놓으면 이렇게 구현할 필요가 없지 않은가?
          }
      
          @Ovrride
          public boolean 청소완료(){
              return super 청소완료() && 담당_교실.실험기구_깨끗함();
          }
      
          @Override
          public boolean 교실_이동(ClassRoom 교실){
              super.교실_이동(교실);
              실험기구닦이_1.교실_이동(교실);
          }
      }
      
    • 나는 위 코드에서 public boolean 교실_이동(ClassRoom 교실) 메서드에서 실험기구닦이_1.교실_이동(교실) 메서드가 필요없다고 생각했음. 왜냐하면 ClassRoom 교실 객체는 부모 클래스에서 상속받아서 사용할 것이라고 생각했기 때문 -
      • 위 교실청소팀 클래스를 상속받으면 모든 속성과 메소드를 받아서 클래스를 만들 수 있음
    • 여러개 클래스가 공통으로 가지고 있는 속성 같은게 있는 문제 발생

  2. 추상 클래스를 상속해서 해결!

     public abstract class 교실_청소당번 {
     	protected ClassRoom 담당_교실;
        
     	public 교실_청소당번 (ClassRoom 교실) {
     		담당_교실 = 교실;
     	}
        
     	public void 교실_이동 (ClassRoom 교실) {
     		담당_교실 = 교실;
     	}
        
     	public void 청소() {  // 이 청소 메소드는 자식클래스에서 다른 역할로 override, 즉 대체해서 쓸 수 있는데 이를 객체지향의 다형성 이라고 함
        
     	}
     }
    
  • 이 추상클래스는 객체를 만들수 없음
    • 개, 고양이 등이 포유류지만 어떤 동물을 보고 포유류라고 하지는 않음.
      • 포유류는 너무 추상적인 개념
    • 추상클래스는 공통분모 역할만을 위한 클래스
    • 위 추상클래스를 통해서 아까 인터페이스에서 했던 것처럼 청소 메소드만으로 모든 클래스들에게 청소를 override시킨 후 바로 반복문에서 청소메소드 호출로 작업 가능
      • 그러면 인터페이스는 쓸모 없는것 아닌가?
        • 그렇지 않다. 상속구조는 상하 관계가 뚜렷한것인데 아래 그림처럼 날치, 박쥐, 날다람쥐는 모두 날기가 가능한 날짐승이란 카테고리에 속할 수 있음. 이를 위한 것이 인터페이스임.

        • 상속은 물려받는것, 인터페이스는 장착