00. 객체지향 프로그래밍 (feat. Java)

AlmondGood·2022년 7월 11일
1

디자인패턴

목록 보기
1/16
post-thumbnail

프로그래밍에 처음 입문하게 되면 보통 어렵고 복잡한 컴퓨터공학 지식보다는 컴퓨터 언어를 주로 접하게 됩니다. 대표적으로 C, C++, C#, Java, Python 등의 언어를 들어 보았을텐데요,
절차지향 언어는 C, 객체지향 언어는 C++, C# 등이 있다는 말만 하고 입문 시기엔 이해가 어렵기 때문에 간단하게만 듣고 넘어갔을 겁니다. 그렇다면 이번 기회에 저와 함께 알아보러 가시죠!


객체지향? 절차지향?

무언가를 알기 위해서는 먼저 개념의 정의부터 찾아 봐야겠죠?

위키백과에 따르면

객체지향 프로그래밍(Objected-Oriented Programming) : 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임

절차적(절차지향) 프로그래밍(Procedural Programming) : 루틴, 하위프로그램, 서브루틴, 메서드, 함수 호출의 개념에 바탕을 둔 프로그래밍의 패러다임이다. 즉, 수행되어야 할 연속적인 계산 과정 을 포함하고 있다.

라고 합니다.
이해가 어렵네요.

간단하게 어떤 사람이 걸어가고 있다고 생각해 봅시다.

객체지향 프로그래밍 : 어떤 사람은 {왼다리, 왼발, 오른다리, 오른발} 을 가질 수 있고, 이 사람은 "걷기" 라는 동작을 수행할 수 있습니다.
그래서 어떤 사람에게 {왼다리, 왼발, 오른다리, 오른발} 을 달아주고, 다리와 발이 있다면 "걷기" 를 시켰습니다.

절차적(절차지향) 프로그래밍 : 어떤 사람에게 "왼다리와 왼발, 오른다리와 오른발" 이 있는지 확인하고, 다리와 발이 있다면 "걷기" 를 시켰습니다.

차이점이 눈에 들어오시나요? 언뜻 봐서는 비슷해 보이지만 객체지향은 각 다리, 발을 달아준 다음 동작을 수행시켰고, 절차지향에서는 어떤 사람에게 다리와 발이 있는지 확인한 뒤 동작을 수행시켰죠.

그렇다면 코드로 볼까요?


// 객체지향
class Person {
	boolean leftLeg;
    boolean rightLeg;
    boolean leftFoot;
    boolean rightFoot;
       
 	Person() {
	boolean leftLeg = true;
    boolean rightLeg = true;
    boolean leftFoot = true;
    boolean rightFoot = true;
    }
       
	void walking() {
   		 if (...) {
       		  System.out.println("걷고 있습니다.");
         }
    }
}
 
public static void Main(String[] args) {
    Person person = new Person();
 
    person.walking();
}

  // 절차지향
  public static void Main(String[] args) {
       boolean personLeftLeg = true;
       boolean personRightLeg = true;
       boolean personLeftFoot = true;
       boolean personRightFoot = true;

       walking();
   }

   public void walking() {
       if (...) {
       System.out.println("걷고 있습니다.");
       }
   }

위에서 보이듯이 절차지향 프로그래밍이 좀 더 쉬워보입니다. 하지만 만약 사람이 하나가 아니라 두 명, 아니 백 명이라면 어떨까요? 한 명만 관리한다면 괜찮지만 사람이 늘어날수록 관리가 힘들어지겠죠. 개개인의 독립적인 객체를 만들어 관리하는 것이 바로 객체지향 프로그래밍인 것입니다.

하지만

여기서 단어를 정정하고 넘어가겠습니다.
사실 절차지향이라는 말은 틀린 말입니다. 위의 절차지향의 개념에서 "수행되어야 할 연속적인 계산 과정" 이라고 했었죠. 대부분의 프로그램은 코드가 쓰인 순서에 따라 절차적으로 실행됩니다. 따라서 객체지향 프로그래밍에서도 "절차적"으로 코드가 실행됩니다.
언어 자체가 객체지향인 것이 아니고, 객체지향의 반댓말이 "절차지향"도 아니며, "절차지향"이라는 말은 "절차적"이라는 말로 바꿔야 합니다.



객체지향 프로그래밍의 4대 특징

객체지향에 대해 이해가 되셨다면 이제 특징을 살펴 보겠습니다.

추상화(Abstraction)

핵심적이고 공통적인 요소들을 모아 한 데 묶는 것.

위의 예시로 보자면 사람에게는 손, 발, 머리 등 많은 구성요소가 있겠죠?

그 중에서도 사람이 걷는데 필요한 발과 다리를 멤버로 Person이라는 "클래스"를 만들었습니다.

class Person {
	boolean leg;
    boolean foot;

    void walking() {
    	...
    }
}

캡슐화(Encapsulation)

우리가 걸어다니는 데 근육이 어떻게 움직이고 관절이 어떻게 움직여야 하는지 계산하면서 다니시는 분이 있을까요? 다리 한번 내딛을 때마다 계산하면서 다니지는 않겠죠.
이처럼 원리를 알지 못해도 사용할 수 있도록 하는 것캡슐화입니다.
마치 캡슐약이 무슨 효능인지는 알지만, 내용물이 무엇으로 구성되는지는 모르는 것처럼 말이죠.


캡슐화는 "정보 은닉"을 실현하기 위한 수단으로 사용됩니다.
예를 들면, 접근제어자를 설정해 다른 사용자 또는 외부 클래스가 내부의 값을 직접 변경하지 못 하도록 하고, 간접적으로 접근할 수 있도록 하는 것이 있습니다.

만약 "걷기" 동작을 세분화해서 아래처럼 표현한다고 해봅시다.

왼다리 들기 -> 왼다리 내리기 -> 오른다리 올리기 -> 오른다리 내리기

그렇다면 Person 클래스와 walking() 메소드의 내부는 밑처럼 됩니다.

class Person {
	boolean leg;
    boolean foot;
 >
    private void 왼다리_들기() {}
    private void 왼다리_내리기() {}
    private void 오른다리_들기() {}
    private void 오른다리_내리기() {}
>
    void walking() {
    	왼다리_들기();
		왼다리_내리기();
		오른다리_들기();
		오른다리_내리기();
    }
}

여기서 다른 클래스들이 있다고 할 때, 필요한 것은 walking() 메소드 뿐입니다. 걷는 과정에 필요한 메소드들은 외부에서 변경되어서도 안 되고 필요도 없습니다. 그래서 private라는 접근제어자로 외부에서의 접근을 막았습니다.

이 외에도 인터페이스, 추상 클래스 사용 등으로 정보 은닉을 실현할 수 있습니다.



https://effectiveprogramming.tistory.com/entry/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%A0%95%EB%B3%B4-%EC%9D%80%EB%8B%89information-hiding%EC%97%90-%EB%8C%80%ED%95%9C-%EC%98%AC%EB%B0%94%EB%A5%B8-%EC%9D%B4%ED%95%B4

상속(Inheritance)

사람의 나이는 다 다르죠, 아기도 있고 어른도 있습니다. 이들도 다 걸어다닐 수 있겠죠.
그래서 Baby 클래스와 Adult 클래스를 만들어 Person을 상속시켜 주었습니다.
각각 Person은 부모 클래스, Baby, Adult 클래스는 자식 클래스가 됩니다

class Baby extends Person {...}
class Adult extends Person {...}

다형성(Polymophism)

하지만 아기는 걷지 못하죠. 그래서 "아기가 걷는다"의 정의를 아기에게 맞게 맞추고 싶어졌습니다.
Person 클래스의 메소드 walking()의 정의를 덧씌웠습니다.

class Baby extends Person {

	@Override 
    void walking() {
    	System.out.println("기어가고 있습니다.");
    }
}

(부모 메소드를 재정의하는 것을 "오버라이딩(Override) 한다"고 합니다!)

이러한 특징들로 인해 객체지향 프로그래밍이 어렵지만 잘 사용한다면 활용성이 뛰어난 방법임을 알 수 있습니다.



객체지향 프로그래밍의 5대 원칙(SOLID)

그렇다면 우리가 객체지향 프로그래밍을 잘 사용하기 위해 지켜야 할 원칙들은 무엇이 있을까요?

SRP : 단일 책임 원칙(Single Responsibility Principle)

객체는 오직 하나의 책임을 가져야 한다.


위의 Person은 '걷기'와 관련된 클래스로 만들었습니다. 갑자기 '웃다'가 튀어나오면 안 되겠죠.

class Person {
	void walking() {...}
 
    // void laughing() {...} 
}

OCP : 개방-폐쇄 원칙(Open-Closed Principle)

객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다.


위의 다형성의 예제를 참고하면 됩니다.
Person의 메소드 walking()의 수정 없이(수정에 대해서 폐쇄), 상속받은 클래스 Baby에서 walking()을 재정의했습니다(확장에 대해서는 개방).

class Baby extends Person {

	@Override 
    void walking() {
    	System.out.println("기어가고 있습니다.");
    }
}

아쉽지만 밑의 3가지 원칙은 아직 잘 이해가 되지 않고 설명이 어려워 자세한 설명이 되어있는 링크를 첨부하겠습니다.

LSP : 리스코프 치환 원칙(Liskov Substitution Principle)

자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다.


https://pizzasheepsdev.tistory.com/9?category=849060

ISP : 인터페이스 분리 원칙(Interface Segregation Principle)

클라이언트에서 사용하지 않는 메소드에 의존하지 않는다.
즉, 큰 덩어리의 인터페이스를 작게 나누어 꼭 필요한 메소드만 이용하게 만든다.


https://pizzasheepsdev.tistory.com/10?category=849060

  • 인터페이스 : 메소드를 직접 정의하지 않고 implements한 클래스에서 구현하도록 일종의 클래스
  • 하나의 인터페이스가 하나의 동작만을 하도록 인터페이스가 분리되어야 한다.

DIP : 의존성 역전 원칙(Dependency Inversion Principle)

추상성이 높고 안정적인 고수준의 클래스는 구체적이고 불안정한 저수준의 클래스에 의존해서는 안 된다.


https://pizzasheepsdev.tistory.com/11?category=849060

  • 최종 구현 클래스에서 하위 클래스 자체를 참조하지 말고, 인터페이스를 통해 추상화한 타입으로 참조하라
profile
Zero-Base to Solid-Base

0개의 댓글