OOP - Object Oriented Programming [ 객체 지향 프로그래밍]

내 할일 잘 하기·2023년 2월 26일
0

Developer

목록 보기
3/8

Object-Oriented Programming

프로그램 설계방법론이자 ‘개념’의 일종. 명령형 프로그래밍에 속한다.
”클래스는 객체이며, 구조체는 객체가 아닌 데이터의 집합”이라는 설명은 틀렸다..!
프로그램을 단순히 데이터/처리방법 으로 나누지 않고 수많은 '객체(object)'라는 기본 단위로 쪼개고 이들간의 상호작용으로 서술하는 방식이다.
객체란 하나의 역할을 수행하는 '메소드와 변수(데이터)'의 묶음으로 봐야 한다.

  • 객체지향을 안쓰면?
    단순한 알고리즘이라면 몰라도, 조금만 복잡해져도 복구 자체가 불가능한 스파게티코드가 완성된다! 남은 물론 코드를 작성한 본인도 유지보수가 어려워진다.

  • 객체지향을 사용해서 얻을 수 있는 장점

    • 코드 가독성이 향상됨. → 이거 하나면 만사 OK..
    • 중복 코드를 어느정도 제거할 수 있음.
    • 자세한 장단점( ref. 나무위키)
      • 데이터 클래스의 상속이라는 개념은 굉장히 뛰어나지만 마찬가지로 굉장히 복잡한 특성을 지니게 해준다. 이 OOP 특성 덕분에 면밀한 자료 분석[5], 개발시간 단축[6], 좀더 정확한 코딩[7]을 보증하지만 코드의 난이도가 급상승한다. 한 마디로 어려워진다. 특히, 다중 상속이 되면 엄청 복잡해진다. 그래서 대다수의 OOP 언어들은 다중 상속을 지원하지 않았고 실제 구현이 전혀 없는 껍데기인 인터페이스만 추가로 상속할 수 있게 했는데, 이게 또 코드의 재사용성을 현저하게 떨어뜨리는 문제가 있어서[8] 최근엔 믹스인이나 트레이트같이 다중상속을 할 수 있는 방법을 찾고 있다. 상속이 복잡하게 얽혀 소스 분석이 어려워진 상태는 라자냐 코드라고 곧잘 불리는 편.

      • 클래스는 오로지 관련 데이터만을 정의하기 때문에, 한 클래스의 인스턴스가 수행될 때 다른 프로그램의 데이터를 절대로 건드릴 수 없게 된다. 덕분에 높은 시스템 보안을 제공하고, 자료 훼손을 방지하는 효과가 있다. 하지만 public 변수를 남발해 버리면... 더 이상의 자세한 설명은 생략한다. 당연하지만, 이는 프로그램 내에서의 각 인스턴스간 내부적인 접근의 범위를 제한하는 것이다. 외부 프로그램의 메모리 접근 문제와는 다른 범주. 외부에서의 메모리 접근까지 차단한다면 메모리 변조는 어떻게 된단 말인가. 애초에 운영체제에서 메모리 관리도 못하게 될 것이다. Visual Studio 6.0의 C++는 데이터베이스의 자료 처리 시 변수를 모두 public으로 처리했었다. 이 이유는 데이터베이스의 필드가 50개이면 get과 set 메소드를 모두 구현해주면 100개의 메소드가 만들어져야 했기 때문이다. 변수 50개와 메소드 100개를 다 150개를 키보드로 쳐야 한다.[9] 생각만 해도 일할 기분이 안난다. 이건 Java도 마찬가지다. 변수의 get과 set 메소드 구현은 어찌됐든 굉장히 귀찮은 작업이다. 데이터의 은닉도 중요하지만 프로그래머의 손가락 건강도 생각해 봐야 한다. C#은 2.0부터 프로퍼티 하나만 선언해 주면 되기 때문에 큰 문제는 없다.구체적인 해당 사례를 알 수 없으니 원문을 남겨두지만, C++ 컴파일러라면 어느 것이든 상속과 타입 선언, 오버로딩 등을 사용하면 public 변수를 사용하는 것과 비슷한 코스트로 메소드를 사용한 입출력을 구현할 수 있다. 메소드가 없으면 클라이언트의 변화무쌍한 갈아엎기에 대한 대응력이 떨어져서 오히려 더 피곤해질 위험이 높은 코드다.

      • 클래스의 정의는 최초로 생성한 프로그램뿐 아니라 다른 OOP에서도 똑같이 사용될 수 있다. 그리고, 이런 이유로 네트워크에 쉽게 분산 사용이 가능하지만 프로그래머에게는 아주 힘든 노력을 강요한다. 네트워크 통신이라는 것은 호환성을 위해 7bit ASCII 코드로 전송한다. 1960년대 만들어진 통신기기를 사용하는 곳도 아직 있기 때문이다. 이 때문에 Quarter-Print, Base64나 UTF-8 같은 것이 만들어진 것이다. 결국 객체도 7bit ASCII 코드로 전송을 해야 한다. 메모리상의 객체 정보를 ASCII 코드화 하는 것을 직렬화(Serialization)라고 하고 ASCII 코드를 다시 객체화 하는 것을 역직렬화(Deserialization)라고 한다. 하지만 마법처럼 그냥 되는 게 아니고 "직렬화 인터페이스"를 프로그래머가 직접 구현했을 때만 가능하다. Java를 비롯한 대다수의 객체 지향 언어들은 직렬화-역직렬화 인터페이스를 기본적으로 제공하고 있다. 이러한 아이디어를 발전시킨 것이 CORBA와 MS의 COM/DCOM/COM+이다. 최근에는 SOAP나 JSON, XML-RPC 등 텍스트 기반의 직렬화 기술도 많이 사용된다. Java에는 Java Runtime끼리 통신하기 위한 RMI(Remote Method Invocation)도 지원한다.

      • 데이터 클래스 개념은 언어에 정의되지 않은 새로운 데이터 형식을 프로그래머가 임의로 정할 수 있도록 해준다.

      • 캡슐화와 격리 구조 설계로 인한 성능 하락이 있다. 거의 대부분의 객체지향 언어에서 기능을 묶으면 결국 함수 호출이 추가로 들어가거나 계산식 중간에 포인터 연산 등이 필요해지며, 멤버 함수 같은 경우 어느 객체의 함수인지 지정해야 하기 때문에 추가 포인터 크기와 연산 비용이 들어간다. 인라인 함수와 컴파일러 최적화(특히 RVO(Return Value Optimization)) 등의 방법으로 어느정도는 격차를 줄여주나 역시 그냥 절차적 프로그래밍보다는 무거워진다.

      • 개념을 기준으로 나누다보니 반복 연산이 컴퓨터 친화적이지 않고, 특히 배열 자료구조를 적용하기 힘들어진다. 객체 하나하나를 따로 캡슐화시키고 상속시 부모만 같으면 자식의 종류를 신경쓰지 않다보니 각자의 메모리 크기가 달라지며, 결국 고정된 연속 메모리에 담을 수가 없게 된다. 메모리 할당을 배열로 하지 못하게 되니 따로따로 생성하게 되는데 이렇게 각각의 객체의 생성과 파괴가 반복되면 메모리 단편화라는 문제가 생기게 된다. 가비지 컬렉션 기능이 만들어진 이유 중 하나. 또 연속 메모리를 쓰기 힘들어진다는 건 캐시의 효율적 사용에 큰 문제가 생긴다는 뜻이기도 해서 성능 격차는 더 벌어지게 된다. 이를 해결하는 코딩 패러다임으로 DOP(Data Oriented Programming)가 있고 구현 방법으로 메모리 풀이 있다.

      • 객체 하나하나를 따로 나누는데 주력하다보니 서로 비슷한 처리를 하는 코드가 서로를 건드릴 수 없게 되었고 이를 해결하기 위해 getter(접근자: 값을 반환하는 메소드), setter(설정자: 필드에 값을 저장하는 메소드)사용이 너무 많아졌다. 이 과정에서 캡슐화가 깨지고 그냥 public으로 공개한 경우나 마찬가지인 상태가 되어 의미가 퇴색되었고, 다른 프로그래밍 패러다임이 필요해졌다. AOP(Aspect Oriented Programming)는 모든 코드 하나하나를 별개의 객체로 분리하기보다 '어떤 일을 어디서 처리하는가' 에 더 중점을 두어 큰 범위로 묶어주어 모듈화 효율을 개선시켰고, 현대 프로그래밍 언어와 코드들은 원본 객체 지향 방식을 그냥 그대로 적용하는 경우가 드물다.
  • 하향식 프로그래밍에서, 상향식 프로그래밍으로
    네이밍 스페이스가 포화되고, 그로 인한 데이터의 오염이 발생되는 하향식 프로그래밍을 극복하기한 방안으로 OOP가 등장했다.
    ’프로그램'이라는 큰 문제를 해결하기 위하여 작은 여러개의 문제로 나누어 해결하던 하향식 프로그래밍에서 ‘작은 문제’를 해결할 객체를 만들고 이 객체들을 조립하는 상향식 프로그래밍으로 발전하였다. OOP가 제대로 적용되기 위해서는 독립성/신뢰성이 높은 객체로 만들어야 재사용성이 좋다.

  • 디자인 패턴?
    객체 지향 프로그램이 복잡해지면서, 객체가 간결하게 정리될 필요성이 생겼다. 디렉토리를 깔끔하게 정리해야 하는 것과 마찬가지.
    디자인 패턴이란, 프로그래밍 형식을 정하는 일종의 약속이며 이는 협업을 전제로 한 환경에서 특히 강조되고 있다.

    [ https://namu.wiki/w/디자인 패턴 ]

  • 객체 지향의 요소

    • 캡슐화(encapsulation)
      변수와 함수를 하나의 단위로 묶는 것. = 데이터의 번들링이다.
      • 정보 은닉(information hiding)
        프로그램의 세부 구현을 특정 모듈의 내부로 감추어 외부로 드러나지 않도록 한다. 내부의 구현은 감추고 모듈 내에서의 응집도는 높인다. 다른 모듈과의 결합도는 떨어뜨리도록 하여 유연함과 유지보수성을 높인다. 특정 객체의 외부에서는 해당 객체의 바깥으로 노출된 특정 메소드에만 접근이 가능하고 클래스 내부에서는 어떤 방식으로 처리되는지 모르도록 설계한다.
        캡슐화와 정보 은닉을 같은 것으로 생각하는 경우가 많은데, 정보 은닉은 캡슐화로부터 파생된 보조 개념이며 '캡슐화 = 정보 은닉'은 아니다.
        파이썬은 정보 은닉을 지원하지 않지만 클래스를 통한 캡슐화를 지원하기 때문에 객체지향 언어로 분류된다.

    [ https://namu.wiki/w/상속(프로그래밍) ]

    • 상속(inheritance)
      자식 객체가 부모 객체의 특성과 기능을 그대로 물려받는 것. 기능의 일부분을 변경해야 할 경우 자식 객체에서 상속받은 그 기능만을 수정해서 다시 정의하게 되는데, 이러한 작업을 '오버라이딩(overriding)'이라고 한다. 상속은 캡슐화를 유지하면서도 객체의 재사용이 용이하도록 해 준다.

    • 다형성(polymorphism)
      하나의 변수, 또는 함수가 상황에 따라 다른 의미로 해석될 수 있는 것.
      • 서브타입 다형성(subtype polymorphism / inclusion polymorphism / subtyping)
        우리가 일반적으로 접하는 OOP의 그것. 기초 객체 또는 어떠한 인터페이스를 구현하는 상위 객체를 생성하고, 해당 객체를 상속받는 다수의 하위 객체들을 만들어 상위 객체의 포인터나 참조변수 등이 하위 객체를 참조하게 하는 것이다. 이 때 각각의 하위 객체는 상위 객체의 메소드 위에 자신의 메소드를 덮어쓰는 메소드 오버라이딩(method overriding)을 수행하며, 상위 객체의 참조변수가 어떤 하위 객체를 참조하느냐에 따라 호출되는 메소드가 달라진다.

      • 매개변수 다형성(parametric polymorphism)
        타입을 매개변수로 받아 새로운 타입을 되돌려주는 기능이다. 타입 매개변수를 정의한 객체 혹은 메소드는 사용할 때 매개변수에 타입을 지정하게 되며, 컴파일 시 지정한 타입에 따라 해석된다.

        • 템플릿(template)
          C++에서 사용하는 개념으로, 타입 매개변수를 입력한 타입으로 치환한 코드를 생성하는 방식이다. 타입 뿐 아니라 변수도 입력할 수 있으며, 객체 내부에서 연산이나 함수 호출을 할 수 있지만, 해당 연산이나 함수가 정의되지 않은 타입을 매개변수로 넣으면 컴파일 에러가 발생하며 컴파일이 느려지고 파일이 커진다.

        • 제네릭(generic)
          Java와 C# 등에 도입된 개념으로, 지정한 타입 매개변수에 해당하는 타입만을 사용하겠다고 약속하는 방식이다. 타입 매개변수가 특정 객체를 상속할 경우 상속하는 객체의 함수는 호출할 수 있지만 그렇지 않을 경우 타입 매개변수로 지정된 객체의 멤버에는 접근할 수 없다.

      • 임시 다형성(ad hoc polymorphism)

        • 함수 오버로딩(function overloading)
          C++과 C#, Java에서는 함수 오버로딩을 통해 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 할 수 있다. 함수 오버로딩을 너무 많이 사용하면 전체적인 코드의 유지보수가 어려워지므로, 템플릿 또는 제네릭으로 대체하는 것이 일반적이다.

        • 연산자 오버로딩(operator overloading)
          C++, C# 등에서는 연산자를 오버로딩해서 기본 연산자가 해당 클래스에 맞는 역할을 수행하게 하는 것이 가능하다. Java에서는 연산자의 오버로딩이 불가능하다. Perl 6나 Smalltalk, F#, Kotlin 등 연산자의 신규 정의가 가능한 언어도 있다.

      • 강제 다형성(coercion polymorphism)

        • 묵시적 형 변환(implicit type coercion)
          'double a = 30;'이라는 식이 실행되면 int형 값 30은 double로 묵시적 형 변환이 이루어진다. double은 int보다 크기가 큰 자료형이므로, 이러한 형 변환을 자료형 승급(type promotion)이라고 한다. C++의 변환 생성자에 의한 형 변환도 묵시적 변환에 속하며, 이를 막으려면 생성자 앞에 explicit 키워드를 추가해야 한다.

        • 명시적 형 변환(explicit type coercion)
          'double a = (double)30;'이라는 식은 위와 동일한 결과를 내지만, (double)을 통해 int형 값 30이 double형으로 변환됨을 명시적으로 표현하였다.
profile
함께 일하고싶은 개발자로 기억될래요

0개의 댓글