객체지향설계는 객체를 구별하고, 관계를 구성하는 표현하는 것입니다.  객체들의 관계를 일반화 시켜서는 연관(Association)이라고 하며, 4가지의 Multiplicity, Aggregation, Composition, Dependency 패턴으로 나눌수 있습니다.
 

역시 연관(Association)은 쉽지 않아~



이 4가지의 관계를 구분하는 것은 매우 어려운 일이 지만, 설계에 대한 문서들과 구현상에서 미묘한 차이가 이들 관계들이 가진 특성입니다.


Multiplicity

하나의 인스턴스  또는 인스턴스 컬렉션이 어떤 클래스의 하나의 인스턴스에 링크 되어 있는 것.
1, 0..1, *, 0..*, 1..* 의 관계를 가질 수 있습니다. (일대일, 일대영, 다대다 ....)

Multiplicity 예
public class Circle {
    private Point pointObj;
 
}
 
public class Point {
	private int X_POS = 0;
	private int Y_POS = 0;
 
        public int getXpos() {
            return this.X_POS;
        }
 
        public void setXpos(int xpos) {
            this.X_POS = xpos;
        }
 
        public int getYpos() {
            return this.Y_POS;
        }
 
        public void setYpos(int ypos) {
            this.Y_POS = ypos;
        }
}




Aggregation

A 객체가 B 객체를 포함한다. 메인 클래스는 포함하는 클래스 없이는 존재 할 수 없지만, 포함되는 클래스는 메인 클래스 없이도 존재할 수 있습니다.

main class
public class Car {
	Engine eng;
}

sub class
public class Engine {
	private int CC = 0;
	private int make = 0;
	private String ModelNo = null;
 
	public Engine(int cc,int make,String modelNo) {
		this.CC = cc;
		this.make = make;
		this.ModelNo =  modelNo;
	}
}



Composition

A 객체가 B 객체를 포함한다. B 객체의 생명은 A 객체에 의해 좌우된다.
포함하고 있는 하위 클래스에 의존하는 메인 클래스를 의미합니다. 메인 클래스는 하위 클래스 없이는 존재할 수 없고, 하위 클래스는 메인클래스 없이는 존재할 수 없는 관계.

Main Class
public class Circle {
    private Point pointObj;
 
    public Circle(int radius) {
        this.pointObj.setRadius(radius);
    }
 
    protected void finalize() throws Throwable {
        pointObj = null;
        super.finalize();
    }
}


Sub Class
public class Point {
	private int radius = 0;
 
        public int getRadius() {
            return this.radius;
        }
 
        public void setRadius(int radius) {
            this.radius = radius;
        }
}


 

Dependency

A 객체의 메소드에서 B 객체를 사용한다.
클래스의 속성으로 존재하는 것이 아닌, 메소드에서 포함한다.



객체의 생명시간에 대한 논의도 염두에 두어야 합니다.

Hybrid 님의 블로그 포스트: Association, Aggregation and Composition 에서 3가지를 구분하는 요소로서의 객체 생명시간 에 대한 논의 글이 참고할 만 합니다.

저도 사실 듣기만 했지 럼바우의 원서적은 본 적이 없어서 정확한 출처는 밝히기 어렵네요
다만 아래 링크의 OMT 표기법을 보면
http://students.cs.byu.edu/~pbiggs/survey.html
여기선 아예 집합화(aggregation)와 합성화(composition)의 구분 없이 모두 집합화로만 보며
그것이 필수적인 것인가 부가적인 것인가로 구분하고 있는 것 같고

제가 알기로도 집합화라고 해서 생명시간(lifetime) 자체가 주 객체와 별개로 살아남을 수는 없고
다만 그 생명시간이 주 객체와 얼마나 밀접한가에 따라 구분하는 것으로 알고 있습니다

제가 참고한 책은 'K교수의 객체지향 이야기' 252~254 페이지인데 여기선 이렇게 설명하고 있네요

- 부속품 관계를 집합화와 합성화로 구별하는 조건은 주 객체와 부속 객체간의 생명 시간이 얼마나 밀접하게 연관되어 있는가에 대한 것이다.
- 합성화의 경우 주객체 생성시 부속객체도 반드시 생성되어야 하며 주객체 소멸시 부속객체도 소멸되어야 한다. 즉, 합성화의 경우 주객체와 부속객체의 생명 시간은 정확히 일치한다.
- 생명시간의 관점에서 볼 때 집합화로 모델링된 부속객체와 주객체의 생명시간이 정확히 일치하지는 않는다. 그러나 집합화의 경우에도 주객체가 소멸되면 부속객체도 없어진다는 사실을 인식해야 한다.

이 책에선 사람의 몸에서 머리와 몸통은 필수적인 것으로 합성화에 비유하고, 팔이나 다리는 필요하지만 필수적이진 않다는 점에서 집합화에 비유하고 있습니다.

-- 댓글 중


참조 :
http://www.hiteshagrawal.com/uml/association-in-uml
http://www.hiteshagrawal.com/uml/aggregation-in-uml
http://www.hiteshagrawal.com/uml/composition-in-uml
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트

커플링 (Coupling) 은 응집도(Cohesion)과 함께 소프트웨어 설계의 품질을 판단하는 척도 입니다. 매우 중요한 개념이지만 정확하게 설명하는 글을 찾기가 쉽지 않습니다. 

모든 설계에 관한 글에서 커플링을 느슨하게 하라고 말합니다. 느슨한 커플링 이더라도 너무 많은 커플링은 전체를 타이트 하게 만듭니다. 

 커플링이란 완벽하게 제거되야할 악의 축인 것일 까요?

아래의 포스트는 IEEE SOFTWARE 에 2001년   파울러가 기고한 Reducing Coupling 글의 번역한 것 입니다.

커플링이란 무엇이며, 커플링을 줄이는 방법은 무엇인지 마틴 파울러 로 부터 배우 봅니다.

요런게 커플링? (couple rings ?, coupling !)




번역 :

Reducing Coupling

by 마틴 파울러.
IEEE SOFTWARE J u l y / A u g u s t 2 0 0 1 , Reducing Coupling , by Martin Fowler


커플링(Coupling)은 응집력(Cohesion)과 함께 설계의 품질을 결정하는 가장 오래된 지표 중 하나 입니다. 구조적 설계 때 부터 그 유산은 사라지지 않았습니다. 저는 소프트웨어를 설계 할때 항상 염두에 둡니다. 커플링을 설명하는 방법이 여러개가 있으나 다음으로 요약됩니다: 어떤 모듈을 변경할때 다른 모듈의 변경이 요구된다면 커플링이 존재합니다. 한 측면에서 두개의 모듈이 비슷한 일을 한다면 다른 코드에 중복되기 쉽습니다. 이는 명확하게 중복의 예 입니다.  중복은 언제나 커플링을 수반합니다. 두개의 코드 조각이 명확한 관계를 맺고 있지 않기 때문에 중복 지점을 집어내기란 쉽지 않습니다.

커플링은 또 어떤 모듈이 다른 모듈의 코드를 사용할 때 발생합니다. 아마도 데이터를 접근하거나 함수를 호출할 때 말이죠. 이런 경우에는 코드 중복과는 다르게 명확하게 집어 낼 수 있습니다. 커플링이란 완전히 제거 해야 하는 요소로 대할 수 없습니다. 여러분은 하나의 프로그램을 여러개의 모듈로 나눌 수 있지만 모듈들은 어떠한 방법으로든 커뮤니케이트가 필요합니다. - 다른 측면으로, 다수의 프로그램을 가지고 있을 때, 커플링은 바람직 합니다. 커플링을 완전히 제거하려 한다면 모든 것을 하나에 집어 넣은 거대한 모듈만이 남게 될 것입니다. 그리고 거기에는 매우 많은 커플링이 존재하게 됩니다. - 단지 러그(rug)로 덮어서 감추어 둔 것일 뿐이죠.

커플링은 우리가 통제해야 할 대상인 것입니다. 그러면 어떻게 통제 할 까요? 모든 곳에서 걱정해야 할 대상일까요? 아니면 더 중요한 특정 위치가 있는 걸 까요? 어떤 요소가 나쁜 커플링이라고 생각되며, 용인되는 커플링이란 어떤 것일 까요?

의존에서 살펴보기

높은 레벨의 모듈에서의 커플링에 대해 생각해 보았습니다. 우리가 시스템을 12개 정도의 큰 모듈 조각으로 나누었을때 이것들을 어떻게 묶을 까요?  저는 coarser-gained 모듈에 집중하였습니다. 모든 커플링을 고려하는 것은 불가항력이기 때문이죠. 가장 큰 문제는 상위(upper level)에서의 커플링을 통제할 수 없을 때 라는 것입니다. 저는 여러개의 모듈이 서로 묶여 있다 하더라도 걱정하지 않습니다, 하지만 모듈간의 의존(dependency) 관계의 패턴을 찾습니다. 이때 다이어그램을 이용하는 것이 매우 유용합니다.

제가 의존(dependency) 이라는 용어를 사용할 때는 UML에서 정의 된 것을 의미합니다. 고로 UI 모듈이 도메인 모듈의 어떤 코드를 참조 한다면, UI 모듈이 도메인 모듈에 의존한다고 말 할 수 있습니다.- 함수 호출, 데이터 이용, 도메인 모듈에 정의된 타입을 이용하는 것 말이죠.
어떤 사람이 도메인 모듈을 변경한다면 UI 모델의 변경이 요구됩니다. 의존(dependency)은 단방향성 입니다. : UI 모듈은 항상 도메인 모듈을 의존합니다. 다른 방법은 존재하지 않습니다. 도메인 모듈 또한 UI 모듈을 의존 한다면 우리는 두번째 의존을 가지는 것입니다.

UML 의존은 전이 불가(non transitive)를 의미합니다. UI 모듈이 도메인 모듈을 의존하고, 도메인모듈이 데이터베이스 모듈을 의존 한다면, 우리는 UI 모듈이 데이터베이스 모듈을 의존한 다고 가정할 수 없습니다. 그렇게 가정한다면 우리는 UI 모듈과 데이터베이스 모델을 직접적인 부가 의존을 기술해야 합니다. 이 비전이성(nontransivity)은 도메인모델이 데이터베이스의 변경으로 부터 UI를 격리시킨다는 것을 보여주기 때문에 매우 중요합니다. UI 는 오로지 도메인 모델의 인터페이스가 변경될 만큼 매우 많은 데이터베이스의 변경이 발생 했을 때에만 변경됩니다. 그림 1a 는 UML 표기를 이용한 다이어그램을 보여 줍니다. UML은 객체지향 시스템을 위해 설계 되었지만, 모듈의 기본 개념과 의존은 대부분의 소프트웨어에 적용됩니다.

이런 종류의 고-레벨(high-level) 모듈의 UML 이름은 패키지(Pakage) 입니다. 그래서 이후 부터는 이 용어를 사용하겠습니다. 패키지들 이기 때문에 저는 이런 종류의 다이어그램을 패키지 다이어그램(pakage diagram) 이라고 부르겠습니다. (UML 로 엄격하게 말해서는 클래스 다이어그램 입니다.)

여기에서 기술하려 하는 것은 레이어드 아키텍처(layered architecture) 입니다. 정보 시스템(information system)에서 종사하는 사람은 누구나 익숙 할 겁니다. 정보시스템에서  레이어는 우리가 의존을 생각할 때 반드시 고려해야 하는 어떤 것을 기술하는 좋은 재료입니다.

의존구조를 심사숙고하는 충고의 가장 공통적인 것은 순환을 회피 하라는 것 입니다. 순환은 여러분의 모든 변경이 다시 최초 패키지의 변경을 요구하기 때문에 매우 큰 문제입니다. 그런 시스템은 수차례에 걸쳐 순환하며 살펴 봐야 하기 때문에 이해하기가 매우 어렵습니다.  저는 엄격한 규칙으로서 패키지들 사이의 순환 회피를 보여주여줄 필요를 느끼지 않습니다.-그것들이 지역화 되어 있다면 저는 관대할 겁니다.




매퍼 패키지(A mapper pakage)

그림 1a, 모든 의존은 한 방향으로 작동합니다. 이는 잘 통제되고 있는 의존 집합임을 나타냅니다.(필수 요건은 아닙니다만). 그림 1b,는 매퍼 패키지가 데이터베이스로 부터 도메인을 분리시켜주는 정보시스템의 또다른 일반적인 요소를 보여 줍니다(매퍼 패키지는 양방향 격리를 제공합니다.), 이것은 도메인과 데이터베이스의 변경이 서로 독립적이 될 수 있도록 합니다. 그 결과로 여러분은 더욱 복잡한 객체지향 모델에서 이런 스타일을 자주 발견할 수 있습니다.

물론, 여러분이 데이터를 불러오거나 저장할때 이 그림이 정확하게 옳지는 않단느 것을 알게 될 겁니다.  도메인의 모듈이 데이터베이스로 부터 데이터가 필요할 때 어떻게 요청하지? 매퍼에게 물어 볼 수 없습니다. 그게 가능하다면 도메인에서 매퍼로의 의존이 나타나게 되고, 순환 의존이 됩니다. 이 문제를 풀기 위해서 우리는 다른 종류의 의존이 필요합니다.

그래서, 저는 다른 부분의 코드를 사용하는 용어로서의 의존을 말해 왔습니다. 하지만, 다른 종류의 관계가 존재 합니다.- 인터페이스(Interface) 와 구현 (implementation)과의 관계 처럼 말이죠. 구현은 인터페이스를 의존 하지만 역(반대)은 아닙니다. 오로지 인터페이스 만을 의존하는 인터페이스를 호출하는 특정 상황에서도, 심지어는 분리된 모듈이 그것을 구현한다고 해도 말이죠.



그림 2는 이 아이디어를 묘사합니다. 도메인은 구현이 아니라 인터페이스를 의존합니다. 도메인은 몇몇 매퍼 구현 없이는 일을 할 수 없습니다. 하지만, 오로지 인터페이스의 변경만이 도메인의 변경을 유발 할 수 있습니다.

이런 상황에서 분리된 패키지가 존재하지만 꼭 필요하지는 않습니다.



그림3은 도메인에 포함된 저장 패키지(store pakage)를 보여줍니다. 매퍼까지 포함된 저장 구현을 구현합니다. 이 경우에, 도메인은 매퍼의 인터페이스를 정의 합니다. 도메인 패키지는 저장 인터페이스를 구현한느 어떤 매퍼라도 선택해서 함께 일 할 것이라는 것을 말해 줍니다.


모듈에 분리된 모듈의 구현을 의도하는 인터페이스를 정의하는 것은 의존을 깨고 커플링을 감소시키는 기반 방법입니다.  이런 접근 방법은 다양한 형태로 나타납니다. 가장 기본적인 형태가 call back 입니다. 이 형태에서 호출자는 특정 시그니처를 포함한 함수의 참조를 요구합니다. 자바 세상에서 일반적인 예는 리스너 입니다. 리스너는 어떤 것을 명확화하는 보다 명시적인 클래스 입니다.

다른 예는 모듈에 전달(passes out) 될 수 있는 이벤트(event)를 정의하는 것입니다. 여러분은 모듈의 확인을 듣고(linstening)있는 인터페이스를 정의하는 이벤트를 생각해 볼 수 있습니다. call back 함수의 호출자는 리스너 인터페이스를 정의하고 있는 녀석입니다. 그리고 이벤트의 생성자는 모듈의 호출 되는 명확한 장소를 모릅니다. 결과적으로 여기에는 의존이 없습니다.


저는 뭔가 마무리 하지 못한  느낌이 듭니다. "잘-통제된 의존" 과 같은 단어를 사용하여 모호하게 만들었기 때문이죠. 잘 통제된 의존 집합 과 같은 것을 정의 하려 시도 할때 견고한 가이드를 제공하는 것은 매우 어렵습니다. 명확하게도 이글은 의존의 양을 줄이는 것에 대한 것입니다. 하지만 그것이 전체의 이슈는 아닙니다. 의존의 방향(direction) 과 흐르는(flow) 방법, 큰 순환을 제거하는 것들 또한 중요합니다. 덧붙여서 , 저는 모든 의존을 동일하게 취급합니다. 인터페이스의 넖이를 제외하고는 말이죠.

거기에 의존이 있다는 사실 보다 그 의존이 무엇이냐를 지나치게 걱정하는 것처럼 보일 수 있습니다.


제가 따르는 기본 규칙은 고-레벨 의존들을 시각화하고 합리화 한 다음, 내가 원치 않는 의존을 깨기 위해 구현으로 부터 인터페이스를 분리 하는 것입니다. 설계에 대한 많은 경험적인 판단들 처럼, 이것 또한 끔찍하게 불완전 하게 보입니다. 하지만, 저는 이것이 유용하다는 것을 알게 됐고, 결국에는 상황에 달려 있습니다.

### 끝.


IEEE SOFTWARE J u l y / A u g u s t 2 0 0 1 , Reducing Coupling , by Martin Fowler

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
 객체지향프로그래밍 이란 무엇인가 (OOP)?  포스트에서 객체지향프로그램의 정의를 알아 보았습니다.

객체지향 프로그래밍 이란 캡슐화, 다형성, 상속 을 이용하여 코드 재사용을 증가시키고, 유지보수를 감소시키는 장점을 얻기 위해서 객체들을 연결 시켜 프로그래밍 하는 것 입니다.


정의를 알고 있다 해도 실제 구현에 적용하려 할때 모호함은 여전히 남아 있습니다. 객체지향설계는 나쁜 냄새(bad smell)을 제거하고 5개의 큰 원칙을 (Single Responsiblility Principle, Open-Closed Principle, Liskov Substitution Principle, Dependency Inversion Principle, Interface Segregation Principle) 지키려고 노력 할때 비로서 그 모양을 갖어갑니다.  Robert C. Martin 이 소개한 5가지 거대 원칙은 객체지향 설계의 근간이 되며, 소프트웨어 일반 책임 적용 패턴, 원칙 General Responsibility Assignment Software Patterns, GRASP  이라고 부르는 원칙에서 9가지 항목으로 세분화 됩니다.   

이번 포스트에서는 소프트웨어를 실패로 만드는 설계의 나쁜 냄새가 무엇이며, 객체지향으로 구현하기 위한 설계(Design) 원칙이 무엇인지 얘기해 보겠습니다.

 

Introduction

 우리는 무엇을 발견하려고 UML 다이어그램을 읽을까? 좋은 다이어 그램인지 아닌지 평가하는 기준은 무엇일까?

설계의 품질 - Quality of Design

잘 설계한 시스템은 이해하기 쉽고, 바꾸기 쉽고, 재사용하기 쉽습니다. 개발하는데 특별히 어렵지도 않고, 단순하고 간결하며 경제적입니다.

 

나쁜 설계의 냄새 - Smell of Bad Design

  1. 경직성 - 무엇이든 하나를 바꿀 때마다 반드시 다른 것도 바꿔야 하며, 그러고 나면 또 다른 것도 바꿔야 하는 변화의 사슬이 끊이지 않기 대문에 시스템을 변경하기 어렵다.

  2. 부서지기 쉬움 - 시스템에서 한 부분을 변경하면 그것과 전혀 상관없는 다른 부분이 작동을 멈춘다.( 바보같은 말이지만, 상관 있는 부분은 당연히 변경해야 한다.)

  3. 부동성 - 시스템을 여러 컴포넌트로 분해해서 다른 시스템에 재사용하기 힘들다.

  4. 끈끈함 - 개발 환경이 배관용 테이프나 풀로 붙인 것처럼 꽉 달라붙은 상태다. 편집-컴파일-테스트 순환을 한 번 도는 시간이 엄청나게 길다.

  5. 쓸데 없는 복잡함(과도한 디자인) - 괜히 머리를 굴려서 짠 코드 구조가 굉장히 많다. 이것들은 대개 지금 당장 하나도 필요 없지만 언젠가는 굉장히 유용할지도 모른다고 기대하고 만들었다.

  6. 필요 없는 반복 - 코드를 작성한 프로그래머 이름이 마치 '복사'와 '붙여넣기'같다.

  7. 불투명함 - 코드를 만든 의도에 대한 설명을 볼 때 그 설명에 '표현이 꼬인다'. 라는 말이 잘 어울린다.

  

Agile Software Development - Principles, Patterns, and Practices , Robert C. Martin 

 

로버트 c 마틴의 5원칙을 SOLID 라고 한다. SOLID Motivational Posters, by Derick Bailey

 

The Single Responsibility Principle - SRP

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐 이어야 한다.

 어떤 객체가 너무 많은 다른 종류의 행동을 할 수 있다면, 그것은  다른 객체가 변경 될 때 함께 변경되어야 할 가능성이 많다는 것을 의미합니다. 

가능한 하나 또는 명확하게 관련이 있는 책임을 부여 함으로써 객체는 보다 명확해(Explicit) 지고, 표현가능해 지며(Expressive), 느슨한 커플링(Loose Coupling) 과 높은 응집력(High Cohesion) 을 얻게 됩니다.

 

 The Open-Closed Principle - OCP

 소프트웨어 엔터티(클래스, 모듈, 함수 등등)는 Interface에 대해서는 개방되어야 하지만, 변경에 대해서는 폐쇄되어야 한다.

 단위 테스트를 할 때는 종종 환경에 생기는 변화를 제어하고 싶은 경우가 발생합니다. 예를 들어 Emploee를 어떻게 테스트 할지 생각해 보시죠. Emploee객체는 데이터베이스를 변경합니다. 하지만 테스트 환경에서 실제 데이터베이스를 바꾸고 싶지 않습니다. 그렇다고 해서 단위 테스트를 하기 위해 테스트용 더미 데이터베이스를 만들고 싶지도 않습니다. 그렇다면, 테스트 환경으로 환경을 변경해서 테스트 할 때 Emploee가 데이터베이스에 하는 모든 호출을 가로챈 다음 이 호출들이 올바른지 검증하면 좋을 것입니다.

 엔터티가 외부로 부터 영향을 받지 않고, 내부의 필요에 의해서만 변경하게 하려면 보호막으로 감싸고 노출되지 않아야 합니다. 그러나 다른 객체와 상호 작용하기 위해서는 선택적인 노출이 필요 합니다. 다른 객체와 대화하기 위한 인터페이스와 확장을 위해서는 Open 하고, 이외의 경우에는 Closed 해야 정보가 감추어지고 (Information Hiding) , 객체들끼리 연결하게 되는 끈이 줄어듦으로써 커플링이 타이트 해질 가능성이 줄어 듭니다.

Liskov Substitution Principle - LSP

서브타입(Sub Type)은 언제나 자신의 기반 타입(Base Type)으로 교체할 수 있어야 한다.

LSP에 따르면, 기반 클래스의 사용자는 그 기반 클래스에서 유도된 클래스를 기반 클래스로써 사용할때, 특별한 것을 할 필요 없이 마치 원래 기반 클래스를 사용하는 양 그대로 사용할 수 있어야 한다. 더 자세히 말하자면, instanceof나 다운캐스트를 할 필요가 없어야 합니다. 강조하건데, 사용자는 파생클래스에 대해서 아무것도 알 필요가 없어야 합니다. 파생 클래스가 있다는 사실 조차도.

 LSP는 상속(Inheritance), 다형성(Polymorphism) 과 관련된 원칙이죠. 상속은 코드 재사용 이라는 이유로 과용 될수 있는 기능입니다. 과잉 사용된 상속은 복잡한 계층구조와 커플링을 타이트하게 함으로써 객체지향으로 얻기 위한 유지관리비용 감소에 악영향을 미치는 요소중 하나 입니다. LSP란 상속을 사용할때 지켜야 하는 원칙을 말하는데요. 상속은 코드 재사용을 위해서가 아니라 명확한 가족 관계가 있을때 사용해야 하며, 그 행위(Behavior) 와 연관이 있습니다. 부모와 파생된 객체들이 존재 할때 가능한 모두 동일한 메소드 이름과 갯수를 가지게 함으로써  언제든지 서브 타입이 기반타입으로 교체될 수 있어야 함을 말합니다. 

다시 말하면, 상속의 오용은
가족이 아닌 객체를 비슷한 일을 하는 것을 보고 코드 재사용 욕심에 가족 관계로 묶는 것.
다층 상속으로 계층 구조가 깊고 복잡하게 하는 것.
파생 타입에 부모, 형제들과는 전혀 동떨어진 능력을 부여 함으로써 돌연변이를 만들어 버리는 것.
을 의미하며 LSP는 돌연변이(부모, 형제들의 형질과 관련이 없는 능력을 가진 파생타입 이며 이렇게 될때는 이미 가족이 아님을 의미)가 발생하지 않도록 관리해서 상속 관계의 타입들이 유연하게 대체 될 수 있게 하는 원칙 입니다. 

LSP의 더욱 자세한 설명은 LSP in Depth  포스트를 참조

 

Dependency Inversion Principle - DIP

  1. 고차원의 모듈은 저차원의 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존한다.
  2. 추상화 된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다. 

자주 변경되는 구상 클래스(Concreate class)에 의존하지 마라. 만약 어떤 클래스에서 상속받아야 한다면, 기반 클래스를 추상 클래스로 만들어라. 만약 어떤 클래스의 참조(Reference)를 가져야 한다면, 참조 대상이 되는 클래스를 추상 클래스로 만들어라. 만약 어떤 함수를 호출해야 한다면, 호출 되는 함수를 추상 함수로 만들어야 합니다.

DIP는 그 용어 자체의 어려움 때문에 이해하기가 쉽지 않지만 내용은 단순합니다. 추상화된 것 (Absctract Class, Interface) 을 사용해야 커플링을 느슨하게 만들 수 있다는 말 입니다. 예를 들어

abstract class Car {} 로 부터 상속 받은 class Truck : Car {}, class Bus : Car {} 가 존재 할때,

Truck porter = new Truck() 과 같이 사용하지 말고,

Car myCar = new Truck();  과 같이 추상 클래스를 기반으로 작업하거나.


    abstract class Car
    {
       public abstract void Run();
       public abstract void Stop();
    }

    class Truck : Car
    {


        public override void Run()
        {
            throw new NotImplementedException();
        }

        public override void Stop()
        {
            throw new NotImplementedException();
        }
    }

    class Bus : Car
    {

        public override void Run()
        {
            throw new NotImplementedException();
        }

        public override void Stop()
        {
            throw new NotImplementedException();
        }
    }


    static void main()
    {
        Truck myCar = new Truck();
        myCar.Run();

        Car myCar = new Truck();
        myCar.Run();

    }

 인터페이스를 사용하는 경우.

  abstract class Car
    {

    }

    interface IDrive
    {
        public void Run();
        public void Stop();

    }

    class Truck : Car, IDrive
    {



        #region IDrive 멤버

        public void Run()
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    class Bus : Car, IDrive
    {


        #region IDrive 멤버

        public void Run()
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }

        #endregion
    }


    static void main()
    {
        Car myCar = new Truck();
        IDrive drive = (IDrive)myCar;

        drive.Run();
        drive.Stop();
    }

 

와 같이 추상적인 것에 구체적인 것을 대입해 사용함으로써 커플링을 느슨하도록 만드는 원칙 입니다.

DIP 에 대한 상세한 개념에 대해서는 '의존 관계 역전의 원칙 Dependency Inversion Principle' 포스트를 읽어 보세요.


Interface Segregation Principle - ISP

클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안된다. 

비대한 클래스(Fat class) 란 메소드를 몇십 몇백개 가지는 클래스를 가르키는 말이다. 대개 시스템 안에 이런 클래스를 두고 싶어 하지 않지만, 가끔 피 할 수 없는 경우도 있습니다.

비대한 클래스가 거대하고 보기 흉하다는 사실 말고도, 한 사용자가 이 비대한 클래스의 메소드를 다 사용하는 일이 매우 적다는 것도 문제입니다. 즉. 메소드를 몇십개 선언한 클래스에서 사용자는 단지 두세 개만 호출 할 지도 모르죠. 불행하게도 이 사용자들은 호출하지도 않는 메소드에 생긴 변화에도 영향을 받습니다.


ISP 인터페이스 분리의 원칙은 가족관계가 아닐때 같은 행동을 하는 객체들에게 인터페이스를 할당 하고 사용 함으로써 DIP 를 가능하도록 만들고 , 그 결과로 느슨한 커플링과 명시성과 표현성을 얻습니다.





더 읽을 꺼리 :
* 객체지향 5원칙을 예제까지 포함하여 설명한 사내양 님의 글 
* SOLID 설명 캐스트

신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
객체지향설계에 있어서 맨 처음 하는 일은 도메인 분석에 의해서 객체를 뽑아 내는 작업 입니다.

명사 는 객체로, 동사는 메소드로, 상태는 객체의 속성으로 구별해 내는 것이지요. 하지만 이 작업이 말처럼 쉽지는 않습니다. 어떤 명사는 명확하게 객체로 구별이 되는데, 어떤 명사는 모호하며, 더 어려운 일은 메소드가 어느 객체에 할당 되어야 하는지 구별하는 것입니다. 또한, 도메인 분석에 의해 구별된 객체 이외에도  관계 및 로직을 구현하기 위해서는 부가 객체들도 필요 합니다. 


어떤 객체가 어떤 책임을 질것 이며, 그 책임에 따라 객체에 할당할 메소드를 찾고, 다른 객체와 공유해야 하는 정보를 구별해 내는 방법론이 Rebecca Wirfs-Brock 와 Brian Wilkerson 이 제안한 Responsibility-driven design, RDD 입니다.

Responsibility-driven design 은 client/server 모델에서 영감을 얻었는데요, 질의에 의한 계약에 집중합니다 : 

1. 이 객체가 책임질 액션은 무엇인가?
2. 이 객체가 공유(Share) 해야할 정보(Information)는 무엇인가?


객체에 책임을 할당 해야 하는데 그 할당하는 패턴 또는 원칙이 General Responsibility Assignment Software Patterns , GRASP 패턴, 또는 원칙 입니다.

객체의 책임을 부여하는 일은 항상 어려운 일 입니다. 마틴 파울러은 '리팩토링 Refactoring' 에서 책임부여의 어려움을 이렇게 말하고 있습니다.

객체 디자인에서 가장 기본이 되는 것 중의 하나(원칙은 아닐지라도)는 책임을 어디에 둘지를 결정하는 것이다. 나는 십년 이상 객체를 가지고 일했지만 처음 시작할 때는 여전히 적당한 위치를 찾지 못한다. 늘 이런 점이 나를 괴롭혔지만, 이제는 이런 경우에 리팩토링을 사용하면 된다는 것을 알게 되었다.
- "리팩토링", 마틴 파울러 , 윤성준 역, 대청미디어, 2002, p.169


GRASP은 이 어려움을 돕기 위한 책임 부여 원칙인 것이지요.

GRASP 은 Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations. 의 9가지 항목이며, 객체지향디자인 5원칙의 각론으로 볼수 있습니다.

책임은 매우 중요하다. 책임 진다고 하면 의외로 쉽게 해결되는 것도 있다.




9가지 항목에 대한 기술은 김대곤님의 글로 대체 합니다.

김대곤 님의 GRASP 패턴 에서 발췌

GRASP 패턴은 아홉 가지로 구성되어 있다. 사실 각 패턴들이 너무 간단해서 싱거울 정도이다.
  1. Information Expert: 역할을 수행할 수 있는 정보를 가지고 있는 객체에 역할을 부여하자. 단순해 보이는 이 원칙은 객체지향의 기본 원리 중에 하나이다. 객체는 데이터와 처리로직이 함께 묶여 있는 것이고, 자신의 데이터를 감추고자 하면 오직 자기 자신의 처리 로직에서만 데이터를 처리하고, 외부에는 그 기능(역할)만을 제공해야 하기 때문이다.

  2. Creator: 객체의 생성은 생성되는 객체의 컨텍스트를 알고 있는 다른 객체가 있다면, 컨텍스트를 알고 있는 객체에 부여하자. A 객체와 B 객체의 관계의 관계가 다음 중 하나라면 A의 생성을 B의 역할로 부여하라.
    - B 객체가 A 객체를 포함하고 있다.
    - B 객체가 A 객체의 정보를 기록하고 있다.
    - A 객체가 B 객체의 일부이다.
    - B 객체가 A 객체를 긴밀하게 사용하고 있다.
    - B 객체가 A 객체의 생성에 필요한 정보를 가지고 있다.

  3. Controller: 시스템 이벤트(사용자의 요청)를 처리할 객체를 만들자. 시스템, 서브시스템으로 들어오는 외부 요청을 처리하는 객체를 만들어 사용하라. 만약 어떤 서브시스템안에 있는 각 객체의 기능을 사용할 때, 직접적으로 각 객체에 접근하게 된다면 서브시스템과 외부간의 Coupling이 증가되고, 서브시스템의 어떤 객체를 수정할 경우, 외부에 주는 충격이 크게 된다. 서브시스템을 사용하는 입장에서 보면, 이 Controller 객체만 알고 있으면 되므로 사용하기 쉽다.

  4. Low Coupling: 객체들간, 서브 시스템들간의 상호의존도가 낮게 역할을 부여하자. Object-Oriented 시스템은 각 객체들과 그들 간의 상호작용을 통하여 요구사항을 충족시키는 것을 기본으로 한다. 그러므로, 각 객체들 사이에 Coupling이 존재하지 않을 수는 없다. 이 패턴은 요구사항은 충족시키면서도 각 객체들, 각 서브시스템 간의 Coupling를 낮은 수준으로 유지하는 방향으로 디자인하라고 말하고 있다. Low Coupling은 각 객체, 서브시스템의 재 사용성을 높이고, 시스템 관리에 편하게 한다.

  5. High Cohesion: 각 객체가 밀접하게 연관된 역할들만 가지도록 역할을 부여하자. 이 패턴은 Low Coupling 패턴과 동전의 양면을 이루는 것으로, 한 객체, 한 서브시스템이 자기 자신이 부여받은 역할만을 수행하도록 짜임새 있게 구성되어 있다면, 자신이 부여 받은 역할을 충족시키기 위해 다른 객체나 시스템을 참조하는 일이 적을 것이고, 그것이 곧 Low Coupling이기 때문이다.

  6. Polymorphism: 객체의 종류에 따라 행동양식이 바뀐다면, Polymorphism 기능을 사용하자. Object-Oriented 시스템은 상속과 Polymorphism(다형성)을 지원한다. 만약 객체의 종류에 따라 행동이 바뀐다면 객체의 종류를 체크하는 조건문을 사용하지 말고, Object-Oriented 시스템의 Polymorphism 기능을 사용하라.

  7. Pure Fabrication: Information Expert 패턴을 적용하면 Low Coupling과 High Cohesion의 원칙이 깨어진다면, 기능적인 역할을 별도로 한 곳으로 모으자. 데이터베이스 정보를 저장하거나, 로그 정보를 기록하는 역할에 대해 생각해 보자. 각 정보는 각각의 객체들이 가지고 있을 것이다. 이 때 Information Expert 패턴을 적용하면, 각 객체들이 정보를 저장하고, 로그를 기록하는 역할을 담당해야 하지만, 실제로 그렇게 사용하는 사람들은 없다. 이것은 그 기능들이 시스템 전반적으로 사용되고 있기 때문에 각 객체에 그 기능을 부여하는 것은 각 객체들이 특정 데이터베이스에 종속을 가져오거나, 로그을 기록하는 매커니즘을 수정할 경우, 모든 객체를 수정해야 하는 결과를 가져온다. 즉 Low Coupling의 원칙이 깨어지게 된다. 이럴 경우에는 공통적인 기능을 제공하는 역할을 한 곳으로 모아서 가상의 객체, 서브시스템을 만들어라.

  8. Indirection: 두 객체 사이의 직접적인 Coupling을 피하고 싶으면, 그 사이에 다른 객체를 사용하라. 여기서 말하는 다른 객체란 인터페이스가 될 수 있고, 주로 인터페이스인 경우가 많다. 그런 특별한 경우는 아래에 설명된 Protected Variations 패턴이라고 부를 수 있다.

  9. Protected Variations: 변경될 여지가 있는 곳에 안정된 인터페이스를 정의해서 사용하자. JDBC에 대해서 생각해 보자. JDBC는 일련의 인터페이스들로 구성되어 있으며, 각 데이터베이스 벤더들이 인터페이스를 구현한 Concrete 클래스를 제공하고 있다. 데이터베이스 기능을 사용하는 시스템의 입장에선 각 벤더들이 구현방식을 바꾸었을 때, 자신의 코드를 수정하고 싶지 않을 것이다. 그래서 Driver를 로딩하는 코드를 제외하고는 모두 인터페이스를 사용함으로서 데이터베이스의 변경시에도 Driver 로딩만 바꾸어 주면 되도록 데이터베이스 관련 작업이 필요한 곳에는 안정된 JDBC 인터페이스를 사용한 것이다.


http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)
http://scottlee.tistory.com/13
http://blog.naver.com/eatist/10013301919
http://davidhayden.com/blog/dave/archive/2005/03/27/895.aspx
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트
TAG GRASP, OOD, RDD
근래의 거의 모든 프로그래밍 객체지향이란 개념으로 구현합니다. 또한, 새롭게 제시되는 방법론 들도 모두 객체지향을 기반으로 제시됩니다. 실세계의 사물을 추상화(Abstraction) 하고, 캡슐화(Encapsulation) 하며, 계층구조는 상속(Inheritance)시키며, 부모와 다른 자식의 특성, 행위는 다형성(Polymorphism) 으로
구현된 그것, 바로 객체의 구성으로 프로그램을 만들어 나가는 것을 객체지향 프로그래밍 이라 하는 것이죠.

객체지향 프로그래밍



이렇게 정의가 되어 있어도 다시 나오는 질문이 있습니다 : "그래 알겠는데, 도대체 객체지향이 뭐야?, 뭐가 특별하다는 거지?, 왜 객체지향 방법론을 사용해야 하는데?, 어떻게 하는 거지?"

이와 같은 질문에는 다시 객체지향의 개념이 반복되죠. 끝이 없습니다. 도대체 어떻게 하면 객체지향을 제대로 이해 할 수 있을까요?  이 질문에 대한 잘 기술된 글이 있어 번역 포스트 합니다. 무엇이 객체지향이고, 무엇이 객체지향이 아니며, 객체지향을 만족시키려면 어떻게 해야 하는가? 어떤 이득이 있는 것인가? 를 다룬 글 입니다. 대부분의 관점에 동의 하며 특히 아래의 객체지향의 핵심을 집어내는 정의로써 에릭 에반스의 DDD 에서 이 장점을 취하기 위해서는 어떻게 해야 하는가에 대한 내용을 다루고 있습니다.

객체지향 프로그래밍 이란 캡슐화, 다형성, 상속 을 이용하여 코드 재사용을 증가시키고, 유지보수를 감소시키는 장점을 얻기 위해서 객체들을 연결 시켜 프로그래밍 하는 것 입니다.



번역 :

제목 :  What is Object Oriented Programming (OOP)?
http://www.tonymarston.net/php-mysql/what-is-oop.html


들어가는 말

뉴스그룹이나 포럼에서 나는 다음과 같은 질문을 매우 빈번히 접합니다. : 무엇을 객체지향(OOP) 라고 부르나요?, 뭐가 특별한가요? 왜 해야 하죠? 어떻게 하는 거죠?. 이런 종류의 질문을 하는 사람은 보통 비객체지향(non-OO) 프로그래밍에 경험이 있으며, 방법론의 전환을 위해서 잇점이 무엇인지 알고 싶어 하는 것입니다. 짧던 길던 불행하게도 대부분의 답변은 동화같고 애매모호하며, 의미없는 것들 뿐입니다.

비객체지향 언어로 1,000개 이상의 프로그램과 500개가 넘는 객체지향 기능의 PHP 프로그램을 작성해본 경험으로 이 논쟁에 기여를 해보려 합니다. 몇몇 객체지향 순혈주의자들은 내가 그들의 방법을 따르지 않는다고 생각하고, 나또한 그들의 방법론을 따르는 것을 거부합니다. 이것에 대한 나의 대답은 "세상에는 객체지향을 유일한 방법이란 없다" 입니다. 사람들은 나의 방법이 틀렸다지만, 그들은 전통적인 실수를 하고 있습니다. 내 방법이 틀리지 않은 간단한 이유는 "작동한다" 입니다. 길 막고 물어 봐도 "작동 하는 것이 틀릴 수는 없습니다. 작동하지 않는 것이 옳을 수는 없습니다."  내 방법이 틀린 것이 아니라, 약간 다를 뿐 입니다.

사람들이 전혀 쓸모 없는 답변을 하는 하나의 이유는 그들이 그렇게 배웠으며, 배운 것 이상으로 생각할 능력이 없기 때문입니다. 또다른 이유는 객체지향에 대한 설명들이 막연하고, 여러가지 의미로 해설될 수 있으며, 어떤 것이 해석 될 때는 '잘못 해석' 될 가능성이 있습니다.  심지어 추상화 Abstraction, 갭슐화 Encapsulation, 정보은닉 Information-Hiding 의  어떤 기본 용어들은 사람들 마다 다른 뜻으로 정의하고 있습니다. 사람들이 OOP의 기본개념들에 동의하지 않는 데, 어떻게 구현에 동의 할 수 있겠습니까.


OOP가 아닌 것은 무엇인가?

내가 반박할 첫번째는 non-OOP 에도 이미 존재 하고 있음에도 OOP 에서만 가능한 것처럼 말해지는 것들입니다.

OOP는 실세계 real world 를 모델링 하는 것이다.

OOP는 실세계를 모델링 하기 위해 추상화 Abstraction 을 사용하는 프로그래밍 패러다임이다. 더 나은 도메인 분석과 시스템 디자인의 통합을 제공 함으로써 실세계 모델링을 더 잘 지원한다. 

OOP 가 다른 방법론들 보다 실세계 모델링을 더 잘 지원한다는 것은 쓰레기 같은 말입니다. 모든 컴퓨터 프로그램은 프로세스를 모델링하는 방법을 찾는 것 입니다. 모델이 잘 못 됐다면 소프트웨어도 잘 못 됩니다. 개념적 모델은 실세계의 분석으로 만들어 집니다. 그리고 컴퓨터 소프트웨어는 전적으로 개념적 모델에  기반합니다. OOP는 더 잘 모델링하는 것을 보장하지 않습니다. 모델의 구현방법이 약간 다를 뿐입니다.

"추상화 Abstraction" 또한 해석해야 하는 용어 이고, 잘 못 해석되는 용어 입니다. "추상화의 진정한 의미 이해하기" 의 논의 에서 처럼, 추상화라는 말은  미켈란젤로의 작품을 보아야 할  때, 피카소의 작품을 떠올리게 합니다.

고객에게 필요한 X,Y,Z 기능이 없고, A,B,C 의 기능을 가진 소프트웨어가 만들어지는데는 이유가 있습니다. 고객이 요구사항 분석서에  X,Y,Z 를 언급하지 않았으며, 분석가가 이것을 집어내는 것을 실패했기 때문입니다. 내 오랜 경력에 비추어 이런 일은 빈번하게 벌어집니다.

OOP 에 의해 실세계가 모델로 바로 매핑된다는 것에 모든 사람들이 동의하지는 않습니다. 더 심하게는 프로그램은 세상을 모델링 하는 것이 아니라 세상의 부분만을 모델링 할 수 있다 입니다.


OOP는 코드 재 사용을 위한 것이다.

객체지향시스템은 코드를 재사용 함으로써 생산성을 증대시키고, 품질을 향상시키며, 비용을 축소 시키는 것을 약속한다.

쓰레기같은 말이다. 이것은 코드 재사용이 non-OOP 에서는 불가능하고 OOP 에서만 가능하다는 의미를 내포하고 있습니다. 코드 재사용은 코드를 어떻게 작성 하느냐에 달려 있는 것이지, OOP가 코드 재사용을 보장하는 것이 아닙니다. non-OOP 언어로도 재사용 가능한 라이브러리를 작성하는 것이 가능하며,  OO 언어로도 재사용 불가 코드가 만들어 집니다.

어떤 언어에서 같은 코드 블록이 100군데 이상 중복 되는 것은 언어 능력의 문제가 아닙니다. 또한, 어떤 언어로도 코드 블록을 재사용 가능한 라이브러리로 만들고 100군데에서 호출하게 할 수 있습니다.

OOP에 대해 수년동안 들어온 약속중에 하나는 , 소프트웨어 벤더들이 이미 작성해 놓은 라이브러리를 사용 가능하게 한다는 것입니다. 프로그래머들에게는 클래스를 작성하는 대신에 이미 작성해 놓은 클래스를 사용하라고 말했습니다. 그것은 '바퀴를 재발명하는 것'이라 면서 말이죠. 꿈은 실현되지 않았습니다. OOP 는 많이 약속 하고 조금 배달 한다는 것을 증명했을 뿐이죠.


OOP는 모듈화에 대한 것이다.

어떤 오브젝트에 대한 소스 코드는 다른 오브젝트 코드와 별개로 작성되고 관리되어질 수 있다. 한번 만들어지면 시스템 안으로 쉽게 전달 할 수 있다.

모듈화 프로그래밍은 non-OO 언어에도 수년동안 존재해 왔습니다. 그래서 이 논재는 객체지향이 비객체지향보다 낮다는 설명으로 쓰일 수 없습니다. 모든 언어는 하나의 어플리케이션 소스코드를 한개의 파일에 집어넣을 수 있습니다. 또한 여러개의 작은 모듈로 나누어서 모듈 별로 파일에 넣고, 유지보수하고, 컴파일 할  수 있습니다.

게다가, 여러개의 클래스로 구성된 어떤 소프트웨어는 자동적으로 모듈화 됩니다. 중요한 요소는 모듈 또는 클래스를 잘 디자인 하는 것입니다.


OOP는 플러그를 가능하게 하는 것이다.

특정 오브젝트가 문제가 있다고 밝혀지면, 여러분은 어플리케이션에서 그 오브젝트를 간단하게 제거하고 다른 오브젝트를 플러그 하듯이 대체할 수 있다. 이는 실세계와 유사하다, 볼트가 부러지면 전체 머신이 아니라 볼트만 대체하면 된다.

이것은 다른 모듈을 손볼 필요없이 개개의 모듈이 독립적으로 수정되고 컴파일 되고 삽입될 수 있다고 말하는 모듈화의 경우와 같다.


OOP는 정보 은닉에 대한 것이다.

객체의 인터페이스와만 상호작용함으로써 내부 구현은 바깥 세상으로 부터 감추어 질수 있다.

첫 부분, 여기서 설명 하는 것은 '구현' 의 은닉이지 '정보'의 은닉이 아니다. 어떤 사람들은 무의식적으로 객체의 데이터가 포함된다고 가정합니다. 다시 말하면, API 뒤로 코드를 감춘다는 것은 뷰로 부터 감춘다는 것이지 객체로 부터 데이터를 감춘다는 말이 아닙니다.

두번째 부분 , 구현 은닉은 OOP의 목적이 아닙니다. 이것은 캡슐화의 부산물 입니다. 바깥 세상은 오브젝트의 메소드 이름만을 볼 수 있습니다. 메소드 뒤의 코드를 볼 수 있는 것이 아닙니다.

세번째 부분, 구현 은닉은  OOP만의 특별한 것이 아닙니다. 인터페이스를 가진 80년대에 작성된 상업용 COBOL 패키지를 기억합니다. 이 패키지의 벤더는 우리의 코드에서 호출해서 사용할 수 있도록 컴파일된 패키지를 제공 했었습니다. 우리는 그 서브루틴의 소스코드를 볼수 없었습니다. 우리는 단지  API 목록과 입력과 출력의 기술서를 받았습니다.



OOP는 메시지 전달에 대한 것이다.

메시지 전달이란 한 객체에서 다른 객체로 데이터를 전달하거나, 다른 객체의 메소드를 실행 시키는 것이다.

객체지향 언어에서 객체가 메소드를 실행시키는 것은 비객체지향 언어에서 함수나 프로시저를 실행시키는 것과 구별되는 것입니다. 비객체지향 언어에서의 함수나 객체 메소드를 실행 시키는 것은 "메시지 전달 Message Passing" 이 아니라 "호출 calling" 이라고 부릅니다. 사실 몇몇 언어에서는 서브루틴을 실행시키기 위하여 "호출" 이라고 부르는 단어가 필요합니다.

non-OO : $result = function(arg1, arg2, ...)
OO :        $result = $object->function(arg1, arg2, ...)

각각의 실행의 결과는 정확하게 동일합니다. - 통제권은 피호출자로 넘어가고 호출자는 기다립니다. 통제권은 피호출자가 종료 될때까지 반환되지 않습니다.

나는 과거에 메시징 스프트웨어를 작업해 보았습니다. 그것이 완벽하게 다르다고 말할 수 있습니다.

첫번째로 메시징 소프트웨어는 한 프로세스에서 다른 프로세스로 메시지를 전달하는 것을 허용합니다. 같은 프로세스에서 다른 모듈로의 메시지 전달이 아닙니다. 같은 어플리케이션의 한 non-Modal 폼에서 다른 non-Modal 폼으로 메시지를 전달 하는 것이었죠. 이것은 오로지 분리된 sendMessage() 함수와 수신 모듈에서의 수신되는 메시지를 처리하기 위한 receiveMessage 트리거 로써 가능합니다. 수신자가 송신자에게 응답하는 방법은 메시지를 전송하는 방법 이외에는 없습니다.

두번째로 행위( Behavior )가 전혀 다릅니다.

그것들은 비동기 입니다. 이것은 호출자는 메시지를 큐에 전송하고 다른 프로세스를 처리합니다. 피호출자가 통제권을 반환 하는 것을 기다릴 필요가 없습니다. E-mail이 전통적인 메시징시스템의 예 입니다.

메시지큐는 프로세스의 개수와 메시지의 갯수에 제약받지 않습니다. 수신 프로세스는 메시지 큐의 첫번째 메시지를 꺼내고, 또 다음 것을 꺼냅니다.

수신자는 메시지를 받자마자 송신자에게 acknowledgement 를 전송합니다. 또는, 처리된 메시지를 반환 합니다.

메시지 시스템은 acknowledgement 또는 결과를 전송하는 코드를 포함하고 있어야 합니다.

보다시피 객체가 메소드를 확성화 시키는 메커니즘은 비객체지향 함수가 활성화 시키는 것과 정확하게 동일합니다. 메시징 시스템의 메시지 전송과는 무관합니다.



OOP 는 책임의 분리에 대한 것이다.

각각의 객체는 구별되는 역할과 책임을 가지고 있는 작은 머신으로 보여질 수 있다.

언어의 문제가 아니라 전적으로 모듈이 어떻게 작성되느냐에 달려 있습니다. COBOL과 같은 절차적 언어로도 독립적인 모듈을 작성할 수 있습니다. 반대로 객체지향 언어로도 지극히 종속적인 모듈이 작성될 수 있습니다.


책임분리의 문제는 그 뜻이 정확히 무엇이든지 상관없이 해석하는 사람마다 다릅니다. 어떤 사람은 SELECT, INSERT, UPDATE, DELETE 와 같은 데이터베이스 오퍼레이션이 그를 필요로 하는 객체에 존재해야 한다고 생각하고, 어떤 사람은 그것들을 한데 모은 별도의 객체에(Data Access Object , DAO) 넣어야 한다고 생각합니다. 어떤 프로그래머는 테이블 마다의 DAO를 작성해야 한다고 생각하고  어떤 프로그래머는 모든 데이터베이스 테이블을 취급하는 한개의 DAO를 작성해야 한다고 생각합니다. 여러분은 책임들을 분리하기 전에 언어와의 별도의 디자인 결정에 의해 책임을 구별해야만 합니다.

내 모든 경험을 통틀어 "기술의 난이도"에 의해 실패한 단 하나의 프로젝트는 "책임의 분리"에대한 모든 것을 알고 있다고 생각하는 객체지향 설계 전문가와 함께 한 것이었습니다. 그들은 각각의 책임을 가지는 모듈로 나누고, 디자인 패턴을 이용하여 설계하였습니다. 이 결과로 UI 에서 데이터베이스 까지는 적어도 10개의 레이어를 가지도록 설계 되었습니다. 이로써 컴포넌트는 필요 이상으로 복잡해 졌고 테스트와 디버깅은 완전히 악몽이었습니다. 그 결과 고객의 시간과 비용은 엄청 증가하였습니다. 그 고객은 플러그를 뽑고 전체 프로젝트를 중단하여 손실을 제한하였습니다. 구식의 비객체 방법론으로 한시간도 채 안걸리는 컴포넌트를 최신 유행하는 객체지향 기술로는 10일이 걸렸습니다.

덧붙여서, "관심이 분리된 Seperation of Concern" 여러개의 클래스/모듈 로 자동으로 구성된 소프트웨어는 특정 엔터티에서는 "걱정거리 Concern"로 고려 되어 질 수 있습니다. 중요한 요소는 클래스 /모듈이 엔터티의 요구사항을 어떻게 잘 취급하는가 입니다.



OOP는 배우기가 쉽다.

OOP는 이전의 접근방법보다 배우기가 매우 쉽다. 이 접근방법은 개발과 유지보수를 단순하게 하며,  다른 절차적 방법론 보다  분석, 코딩, 복잡한 상황의 이해도를 직관적으로 만들어 준다.

마케팅 전략일 뿐입니다. 모든 언어/도구/패러다임은 그 가치보다 모든 것이 좋다고 제안됩니다.  무엇을 사용하느냐가 아니라 어떻게 사용하느냐가 중요합니다. 유능한 프로그래머에 의해 사용되는 '구식' 언어는 '신식'언어의 광고되는 생산성보다 몇배는 더 큽니다.

한 사람의 학습 능력은 종종 가르치는 사람과 가르치는 도구에 의해 제한 됩니다. 나는 프로젝트를 성공 시키는 것 보다 너무 비효율적이고, 복잡하게 하는 것을 배우는 것을 두려워 합니다. OOP를 가능하게 하는 방법은 "단 한가지" 뿐이라고 주장하는 선생님이 너무 많습니다. 나는 이것에 가장 동의하지 않습니다. 나는 소위 전문가 라고 하는 사람들의 방법을 무시하고도  비객체 지향 언어로 작성된 것을 성공적으로 객체지향언어로 마이그레이트한 경험이 많습니다.

어떤 사람은 프로시저 함수를 클래스로 감싸는 것 만이 OOP가 아니라고  나에게 말 했습니다. 나는 동의 하지 않습니다. 그것은 간단 합니다. 유일한 트릭은 연결된 함수를 한곳에 모으고( 캡슐화 라고 말하는) , 객체로 관리될 수 있도록 조정 합니다. 여러분이 클래스로 할 수 있는 정말 똑똑한 것은 부모 또는 추상 클래스를 상속(Inheritance)을 통하여 수개의 서브클래스로 확장하는 것 입니다. 다형성(Polymorphism)을 이용하여 서로 다른 클래스의 메소드/함수를 사용 가능하게 할 수도 있습니다. 나는 함수들이 잘못 혼합된 매우 많은 예제들을 접해 봤습니다. 관계 없는 함수가 한 클래스에 있으면 안됩니다. 나는 상속의 과잉 사용으로 매우 복잡한 계층구조를 가져서 유지보수와 진보가 거의 힘든 경우를 보았습니다.  나는 다른 프로그래머에게 인상적으로 보일 목적 이외로는 볼 수 없는 객체지향의 모든 기능을 사용하여 코드를 더욱 불분명하게 만드는 프로그래머를 본적이 있습니다. 그들은 너무 단순하면 틀렸다고 생각하는 듯 합니다. 아마도 KISS( Keep-It-Simple, Stupid. 단순하게 하란 말야. 멍청아!) 원칙을 들어본적이 없어 보입니다.


OOP는 행위자(actors) 와 행위(actions) 에 대한 것이다.

객체지향 프로그래밍이란 소프트웨어 개발을 행위자와 행위를 코드를 이용하여 분해하고 모듈화하는 것이다.

이것은 매우 모호하고, 전혀 쓸모 없는 말입니다.



OOP 는 늦은 바인딩( late binding )에 대한 모든 것이다.

'늦은' 이라는 것은 (어떤 바이너리를 로드할 지 , 어떤 함수를 호출 할지) 바인딩을 가능한한 늦추는 결정이다. 컴파일 타임에 바인딩 (early) 하기 보다는 호출 될 때 바인딩 하는 것이다.

빠르거나 늦게 바인딩 되는 것은 객체지향과 비객체지향 차이와 무관합니다. 비객체지향 언어도 늦은 바인딩을 지원합니다. 비객체지향 언어가 늦은 바인딩을 지원한다고 해서 마술같이 객체지향으로 바뀌지 않는 것 처럼, 객체지향 언어가 빠른 바인딩을 한다고 해서 비객체지향 언어가 되는 것이 아닙니다.


알겠지만, 위는 너무 불명확하거나 OOP에 한정된 것이 아니어서 구별되는 기능으로 사용될 수 없는 것들을 기술한 것입니다.



객체지향 언어란 무엇인가?

아래의 것들을 제공한다면 그 언어는 객체지향언어 목록에 추가 될 수 있습니다.


Class
Object
Encapsulation
Inheritance
Polymorphism

클래스는 엔터티의 프라퍼티(데이타) 와 그 프라퍼티들에 작동하는 메소드( 함수 또는 오퍼레이션) 들을 정의(캡슐화 Encapsulation) 합디다. 엔터티의 프라퍼티나 메소드는 클래스 바깥에 정의 되어서는 안됩니다.


객체지향 이란 무엇인가?

여러분이 믿고 싶어하는 것보다 훨씬 단순 합니다. 사람들은 실제의 그들 자신보다 더 영리해 보이기 위해 더욱 복잡한 정의를 사용합니다. 여기에 진짜 정의가 있습니다.

객체지향 프로그래밍 이란 캡슐화, 다형성, 상속 을 이용하여 코드 재사용을 증가시키고, 유지보수를 감소시키는 장점을 얻기 위해서 객체들을 연결 시켜 프로그래밍 하는 것 입니다.


객체지향 프로그래밍을 하기 위해서는 객체지향 언어가 필요합니다. 캡슐화, 상속, 다형성을 지원한다면 객체지향 언어라고 말 할 수 있습니다. 다른 기능들도 지원 할 수 있지만 그것들은 그닥 중요하지 않습니다.  이는 개인적인 의견이 아니라 객체지향 용어를 발명한 사람의 의견 입니다. Bjarne Stroustrup 은 그의 문서 Why C++ is not just an Object Oriented Programming Language: 섹션 3 에서 지금의 널리 사용되는 "객체지향" 이라는 용어를 제시하였습니다.

언어 또는 기술은 다음을 직접 지원 한다면 객체지향 이다.
1. 추상화 - 클래스나 객체를 제공한다.
2. 상속 - 이미 존재하는 것으로 부터 새로운 추상화를 만들어 낼 능력을 제공한다.
3. 런타임 다형성 - 수행시간에 바인딩 할수 있는 어떠한 폼을 제공한다.


많은 객체지향 언어들은 나중에 더 많은 기능들을 추가 하였고, 몇몇 사람들은 이 부가 기능들을 객체지향을 결정하는 요소라고 생각하지만 나는 전혀 동의하지 않습니다. 이는 온도조절기나 네비게이션이 없다고 자동차를 자동차가 아니다라고 말하는 것과 같습니다. 이것들은 구별되는 기능이 아니라 선택 사양 입니다.

바퀴가 있다고 해서 자동차라고 말 하는 것 또한 틀린 것입니다. 바퀴를 가졌다고 모든 것이 차가 되는 것은 아닙니다. 손수레에 바퀴가 있다고 해서 자동차가 되는 것은 아닙니다. 이것은 구별되는 기능이 아닙니다. 비객체지향 언어에 이미 존재하는 것이기 때문에 "모듈성", "재사용성", "메시징"을 비객체와 객체를 구별하는 기능이 아니라고 말하는 간단한 이유입니다.


OOP 와 non-OOP 의 차이점



비객체와 객체의 차이점을 설명하는 가장 좋은 방법은 실제 예제를 보는 것입니다.

다르게 정의 됩니다.

함수는 필요한 것을 가진 코드의 블록으로 정의 됩니다. 각각의 함수 이름은 어플리케이션에서 유일해야 합니다.

function fName ($arg1, $arg2)
// function description
{
    ....
   
    return $result;
   
} // fName


클래스 메소드는 클래스 정의의 경계로써 규정됩니다. 각각의 클래스 이름은 어플리케이션에서 유일 해야 합니다. 각각의 클래스는 클래스 내부에서 유일한 이름을 가지는 다수의 함수(메소드라고 알려진)들을 가질수 있습니다. 어플리케이션에서 유일할 필요는 없습니다. 사실 공통의 함수/메소드 이름을 가지는 것은 다형성에서 필요로 하는 공유의 능력을 위해서 입니다.

class cName
{
    function fName ($arg1, $arg2)
    // function description
    {
        ....
       
        return $result;
       
    } // fName
   
} // cName



접근 방법이 다릅니다.

함수/클래스는 그 정의가 로드되기 전에 접근될 수 있음을 주목해야 합니다. 

함수를 호출하는 것은 직접적입니다.

$result = fName($arg1, $arg2);


클래스의 메소드를 호출하는 것은 직접적이지 않습니다. 먼저 클래스의 인스턴스를 생성해야 합니다. 그리고  오브젝트의 함수 이름을 통해서 접근합니다.  오브젝트 이름은 어플리케이션에서 유일 해야 합니다.

$object = new cName;
$result = $object->fName($arg1, $arg2);


다수의 다른 복제본을 가지고 있습니다.

함수는 접근되기 전에 인스턴스화 될 필요가 없습니다. 단 한개의 카피본 만이 존재하기 때문이죠.

클래스 메소드는 오브젝트로 인스턴스화 된 후에만 접근될 수 있습니다. (정적 메소드로 정의된 경우를 제외하고) . 같은 클래스로 서로 이름이 다른 다중의 오브젝트로 인스턴스화 시킬수 있습니다.
 
$object1 = new cName;
$object2 = new cName;
$object3 = new cName; 

 
인스턴스화 시키지 않고 정적메소드에 접근할 수 있다 하더라도, non-Class 함수의 접근보다 나을 것이 없습니다. 오브젝트에 의해 실제로 사용되지 않으며, 객체지향 프로그래밍의 한 부분으로 고려되는 사항이 아닙니다.


다수의 엔트리 포인트를 가집니다.

함수는 단일의 진입점을 가집니다. 그것은 함수 이름 그 차체 입니다.
오브젝트는 여러개의 진입점을 가집니다. 각각의 메소드 이름 입니다.


상태 관리를 위한 다수의 메소드를 가집니다.

함수는 기본적으로 상태를 가지지 않습니다. 이것은 호출 될 때마다 새로운 실행으로 취급된다는 것을 의미합니다. 이전 실행과는 아무 연결이 없습니다.

오브젝트는 상태를 가집니다. 각각의 오브젝트의 메소드가 호출될 때 마다 오브젝트의 상태가 변경됩니다.

이것은 함수와 클래스 메소드 모두 로컬 변수를 사용한다는 점에서 같은 방식으로 동작합니다. 로컬 변수란 함수나 클래스 메소드의 범위를 넘지 않는다는 것을 뜻합니다. 그리고, 실행들 사이에서 저장되지 않는다는 것이죠.


함수가 서로 다른 수행에서 값을 기억할 수 있는 방법은 정적 변수를 사용하는 것입니다.

function count () {
    static $count = 0;
    $count++;
    return $count;
}

이 함수는 호출 될 때마다 이전 호출 값보다 하나더 증가된 값을 반환 합니다.  static 키워드가 없다면 항상 '1' 이라는 값을 반환 합니다.

클래스 레벨에서 선언된 변수를 함수(메소드) 범위 밖으로 저장시키려면 다음과 같이 하면됩니다.


class calculator
{
    // define class properties (member variables)
    var $value;
    
    // define class methods
    function setValue ($value) 
    {
        $this->value = $value;
        
        return;
        
    } // setValue
    
    function getValue () 
    // function description
    {
        return $this->value;
        
    } // setValue
    
    function add ($value) 
    // function description
    {
        $this->value = $this->value + $value;
        
        return $this->value;
        
    } // setValue
    
    function subtract ($value) 
    // function description
    {
        $this->value = $this->value - $value;
        
        return $this->value;
        
    } // setValue
    
} // cName


클래스/오브젝트의 참조는 $this->varname. 과 같은  $this->  프리픽스 에 의해 참조 된다는 것에 주목하세요.
this 키워드로 참조되지 않은 $varname 변수는 로컬 변수로 취급됩니다.

각각 인스턴스는 자신의 변수를 가지고 있습니다. 같은 클래스로 부터 나왔을 지라도 하나의 오브젝트의 컨텐츠는 다른 오브젝트와 독립적입니다.


(실제 예제 - 캡슐화, 상속, 다형성의 예제는 번역에서 생략합니다. 예제는 원문을 참조하세요.)


결론.


많은 사람들이 OOP의 의미를  서로 다른 단어로 묘사합니다. 문제는 그 단어들이 오역되기 쉽다는 것이죠. 루이스 캐롤의 험프티-덤프티가 자칭한 유리창 너머로 볼때 처럼.

내가 이 단어를 사용할 때는 내가 선택한 뜻을 의미해, 더도 덜도 아니야


OOP의 창작자가 사용한 단어를 사용할 때, 그 단어에 다른 뜻을 적용한다면, 다른 사람은 여러분의 단어를 또 다른 뜻으로 적용합니다. 그것은 원래와 전혀 관계 없는 것으로 끝이 납니다.

객체지향과 비객체지향을 구별하는 데에느 단지 세가지 기능이 있습니다. 이것들은 캡슐화, 상속, 다형성 입니다. 이거 이외에는 헛소리 입니다. 객체지향 프로그래밍은 프로그래밍 언어에서 이 기능들을 이용하는 것입니다. 높은 재사용성과 낮은 유지보수 비용은 보장될 수 없습니다. 전적으로 이 기능들을 어떻게 구현하느냐에 달려 있습니다.

몇몇 사람들은 내가 OOP에 대해 너무 단순한 관점을 가졌다고 비난합니다. "필요이상으로 단순화 시켰다거나", "필요이상으로 복잡화 했다 거나" 라고 하는 대신에 말이죠. KISS 원칙의 오래된 추종자로써 나는 다른 사람들을 가르치기 쉬운 더욱 적절한 관점을 알고 있습니다.




더 읽을 꺼리 :
제목 : 구조적 프로그래밍 이란?
http://user.chol.com/~hurcb01/study5_sub7.html
신고

이 글을 Twitter / Facebook 에 공유하기
이 글이 유익하다면 아래의 트위터 버튼을 눌러 공유해 주시거나, 페이스북 "좋아요" 버튼을 눌러 주세요.

   


Posted by 반더빌트


티스토리 툴바