Javascript에 관련된 질문들

1. Java와 Javascript의 다른 점은 무엇인가요?

Javascript 는 애당초 Java 애플릿의 대체자로 만들어지게 된 배경으로 javascript 라는 이름이 사용될뿐 전혀 연관성이 없는 언어이다.

  • java 는 typed static 언어인 반면 javascript는 none typed 언어로서 동적으로 자료형을 검사하게 된다.
  • java는 class 기반 컴파일+인터프린트 oop 언어이지만 javascript 는 prototype 기반의 인터프린트 언어이다.
  • javascript 는 스크립트 언어의 특성상 컴파일 없이 동작하게 됨으로 언어를 번역할수 있는 엔진이 동적으로 모든 사항을 처리하기 때문에 컴파일 언어에 비해 실행이 늦거나 실행이전에 오류 검출이 용이하지 않다.

2. undefined와 undeclared 변수는 무엇인가요?

Javascript는 undefined 라는 특별한 형태가 존재한다. javascript를 대충 공부하게 되면 undefined 와 null 의 차이를 잘 이해하지 못하는 경우도 많다.

모든것이 object 로 통하는 javascript 에서의 null 은 값이 아닌 객체 참조의 연결을 해지하는 것을 말한다. 즉 어떤 참조값이 존재 하지 않음으로 비어있는 값을 가진 변수가 되는 것이다.

undefined 는 선언만 되어지고 특정 값이 할당되지 않는 경우 javascript 엔진에 의해 자동으로 할당되는 값이다. 즉 특별히 할당된 값이 없는경우 일반적인 언어처럼 null 이 아니고 undefiend 가 할당 되는 것이다.

또한 객체가 소유하지 않은 프로퍼티에 접근하게 될 경우에도 undefined 가 반환된다.

undeclared 변수란 선언하지 않고도 사용가능한 변수라고 한다. 하지만 정확히는 이는 javascript 의 scope 개념에서 따져보면 유효범위를 지정하지 않은 변수다.

javascript는 none typed 언어로서 var 라는 키워드를 사용하여 변수를 사용하는게 보편적으로 알려져 있는 사실이다.

하지만 이 var 키워드는 선언을 위한 키워드가 아니고 해당 변수의 유효범위를 지정하는 역활을 한다. 해당 키워드를 지정하지 않은 변수는 global scope 에 저장 된다. 하지만 여기에는 한가지 제약사항이 따른다.

var 키워드 없이 사용된 변수는 할당문을 만나기전에 해당 변수에 참조를 만나면 오류를 내뱉게 된다.

foo = 1;
console.log(foo+2);
>3

console.log(foo+2);
foo=1;
> Uncaught ReferenceError: foo is not defined

javascript 자료형에 대한 정리는 Javascript기초 - 데이터타입 에 정리해 놓았으니 참고 하길 바란다.

3. 클로져(Closure)는 무엇이며, 어떻게/왜 사용하는지 설명해주세요

클로져를 제대로 이해하기 위해서는 javascript 의 scopescope chaincontext 에 대한 이해가 선행 되어야 한다.

간단히 설명하자면 클로저는 현재의 유효범위를 넘어 scope chain으로 연결되어 있는 객체,변수등의 참조를 발생시키는 것을 말한다.

javascript는 실행코드 블럭 단위로 context 를 스텍에 쌓게 되고 push, pop 을 통해 코드블럭이 실행 된다. 이때 각각의 실행 코드블럭이 수행되는 시점에서 실행 환경을 저장하게 되는데 이는 실행 유효범위인 scope 에 의해 결정된다.

이 scope 는 chain구조로 연결되어 있어 현재 실행 시점 이전의 scope 를 타고 올라가는 형태로 참조 되기 때문에 현재 scope 에 선언되지 않는 객체참조가 가능하다.

이는 java 등의 언어만 다루던 사람들에겐 좀 의아한 모습으로 동작한다. local 변수와 global 변수의 경계와 유요범위 설정에 대한 이해를 한번에 무너트려버리기 때문이다.

4. 익명함수(anonymous function)는 주로 어떤 상황에서 사용하나요?

익명함수의 사용은 함수 선언이 아닌 함수표현식을 이용하는 방법이다. 이는 곧 람다함수(함수 리터럴을 변수에 할당하는 방식)와 즉시실행구문을 만들어 낼수 있다는 말이다.

이처럼 즉시실행 구문을 사용하면 javascript 가 유효범위를 선언 할 수 없다고 해도 강제적으로 private 변수를 만들어 내는 것이 가능 하다.

// i 라는 변수는 실행 시점에서만 사용되면 외부에서 접근 할수 없다. 
(function() {
   var i  = 'hello world' ;

{)();

console.log(i)
> error

즉 익명함수는 동적으로 할당되는 유효범위를 가지기 때문에 javascript 내에서 강제적인 유효범위 설정을 하는 경우 사용되게 됩니다.

5. "Javascript 모듈 패턴(Javascript module pattern)"이 무엇인지 설명을 해주시고, 언제 사용하는지도 말씀해주시기 바랍니다.

javascript 의 모듈 패턴은 javascript 의 코드 관리 기법 중하나로서 javascript의 특성상 객체 핸들링을 위한 방법론중 하나이다.

javascript 의 모듈 패턴은 일반적인 유효범위를 설정하는 언어에서와 같이 private 와 public 등의 캡슐화를 사용하는 방법이다.

javascript 에서 함수 혹은 변수객체를 다룰때 중복된 name 사용으로 인한 문제를 방지하기 위해 주로 namespace 방법이 사용된다. 이는 global 영역에 객체 고유의 영역을 지정하고 변수와 함수 할당을 해당 namespace 하위로 두게 하여 중복된 name 으로 인한 오류를 피하는 방법이다.

모듈 페턴은 이 네임스페이스 페턴에 언어적 유효범위를 추가 해논 것이라 이해하면 슆다.

모듈을 작성함에 있어서 return 구문을 이용하여 공개될 영역과 내부적으로 처리할 영역을 구분하여 공개여부를 선택하게 하는 것이다.

// namespace 패턴
var myApp = myApp || {}; // 네임 스페이스 선언

myApp.insanehong = function() {
    return 'insanehong';
};

myApp.helloworld = function() {
    return 'hello world';
}

// 모듈 페턴


var Messages = {h : 'hello', w : 'world', insane:'insanehong'};

var myApp = (function(msg) {
  var helloworld = msg.h+'  '+msg.w;
  var helloinsanehong = msg.h+' '+msg.insane;

  var printInsane = function () {
     return helloinsanehong;
  };

  var printhello = function() {
    return helloworld;
  };  

  return {
    foo1 : printInsane,
    foo2 : printhello
  };

})(Messages);


console.log(myApp.foo1());
> hello insanehong
console.log(myApp.helloworld);
> undefined

6. 호스트 객체(Host Objects)와 네이티브 객체(Native Objects)의 차이점은 무엇인가요?

네이티브 객체는 브라우저 혹은 구동 엔진에 내장되어 있는 객체를 말한다. 네이티브 객체는 built-in 객체와 달리 자바스크립트 엔진이 구성하고 있는 기본객체라고 하기보단 브라우저 혹은 사용되는 자바스크립트 엔진에 영향을 많이 받게 된다. B.O.M 이라는 브라우저객체 모델과 D.O.M 이라는 문서 객체 모델이 네이티브 객체에 포함되는데 이 객체의 사용성이 이를 구현한 구동엔진에 따라 각기 다르게 존재하는 경우가 있기 때문에 크로스 브라우징에 문제를 발생시키기도 한다.

호스트 객체는 빌트인 객체와 네이티브객체에 포함되지 않은 사용자에 의해 생성된 객체를 의미한다. 자바스크립트 엔진은 빌트인 객체와 네이티브 객체를 구성한 이후 호스트객체를 해석하게 된다.

7. 다음 코드의 차이점은 무엇인가요? javascript function Person(){}, var person = Person(), var person = new Person()

function Person() {
  return 'hello world'; 
}

위 코드는 함수 선언이다. 이는 함수 객체 생성을 위한 기본 그릇이 되면 prototype 이 참조할 함수객체의 환경을 담고 있다. global scope 에서는 Person 이라는 함수가 선언되었다는 것만을 저장하면 내부구현 로직은 알지 못한다.

var person = Parson(); 은 함수 수행에 따른 return 을 변수에 저장한다는 의미이다. 즉 person 에는 'hello world' 가 할당된다.

var person = new Person() 은 person 변수에 Person 함수 객체를 생성하여 할당한다는 의미가 된다. 이때 할달되는 객체는 Person 함수의 프로토타입을 기반으로 생성된다.

8. .call과 .apply의 차이점은 무엇인가요?

Function.prototype 이 소유한 method 들이다. 이들은 함수와 메서드가 실행될 때 바인딩할 객체를 지정하여 함수가 실행될때의 context 의 유요범위를 직접 지정하며 this 를 할당 할수 있다.

이들은 호출의 동적인 변화에 따라 각각 다르게 되는데 정적인 호출인 경우 call 을 동적인 호출에서는 apply를 사용하게 된다. 즉 호출시 동적인 인자전달등이 필요할 경우 apply 를 정적으로 고정된 함수를 호출할 경우 call 사용하면 된다.

bind() 메소드나 동적 callback 을 구현할때 apply가 사용되는 이유이기도 한다.

9. Function.prototype.bind을 설명 하시오

8번 문제에서 잠깐 다루었던 내용과 비슷하다. 함수객체는 실행 시점에서 execution context 를 생성하며 현재의 실행 코드 범위를 뜻하는 this 를 할당하게 된다. 하지만 this 를 동적으로 할당해야 하는 경우가 있다. 특히 다양한 객체에서 동적으로 특정 액션을 할당하여 사용되는 함수의 경우 this 에 할당되는 객체를 예측하기가 힘들다.

이럴때 bind 를 이용하여 실행 시점에서 context의 this 를 임의로 할당 해 주어 동적인 호출시에도 오류 없이 코드가 동작하게 할수 있다.

ECMA-262, 5th edition 에 의하면 Function.prototype.bind()는 아래 코드와 같이 구현되어 있다.

if (!Function.prototype.bind) {
    Function.prototype.bind = function() {
        var funcObj = this;
        var original = funcObj;
        var extraArgs = Array.prototype.slice.call(arguments);
        var thisObj = extraArgs.shift();
        var func = function() {
            var thatObj = thisObj;
            return original.apply(thatObj, extraArgs.concat(
                Array.prototype.slice.call(
                    arguments, extraArgs.length
                )
            ));
        };
        func.bind = function() {
            var args = Array.prototype.slice.call(arguments);
            return Function.prototype.bind.apply(funcObj, args);
        }
        return func;
    };
}

10. Javascript에서 어떻게 상속을 하는지 설명할 수 있나요?

Javascript 는 공식적으로 상속을 지원하지 않는 프로토타입 기반 객체 확장을 지원하는 언어이다. 이런 이유로 직접적인 상속을 구현 할수는 없지만 프로토타입 확장을 이용한 상속과 같은 의미를 구현해 낼수는 있다.

간단한 prototype 을 이용한 상속 개념은 아래와 같이 구현 낼수 있다.


function Parent() {
  this.hello ='hello';
}

function Child() {
  this.world = 'world'; 
  this.helloworld = this.hello+' '+this.world;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var Obj = new Child();

console.log(Obj.helloworld);

prototype 관련 내용은 Javascript 기초 - Object prototype 이해하기 을 참조하기 바란다.

11. UA문자열을 이용하여 기능 검출(feature detection)과 기능 추론(feature inference)의 차이점을 설명 하시오.

사실 이문제는 제대로 의도를 파악하지 못했다. 다만 User-Agent 를 이용하여 네이티브 객체를 선택하는 것과 네이티브객체의 유무를 검사하여 분기처리하는 것으로 생각하였다.

User-Agent는 브라우저 별로 공통적인 부분과 독립적인 요소로 나누어지게 되어 구동 엔진을 무엇을 선택하였는가를 알아낼수 있다. 이를 이용하여 이벤트 리스너 등과 같은 브라우저마다 각기 다른 방식으로 구현된 네이티브 객체(함수등)을 분기하여 처리 할수도 있으며 네이티브객체의 존재유무를 검사하여 해당 엔진이 해석가능한 객체 호출을 하는 방법등으로 크로스 브라우징문제를 해결하게 된다.

AJAX에 관해 가능한 자세히 설명하세요.

Asynchronous JavaScript and XML 의 약자로서 XMLHttpRequest 객체를 이용하여 비동기 방식으로 서버와 통신을 하는 것을 말한다.

웹 브라우저의 클라이언트와 서버간 통신은 url를 이용한 http 통신으로 이루어진다. 즉 브라우저가 서버로 request를 날리기 위해서는 해당 브라우저의 url 주소를 변경하여야 하는데 이때는 페이지 이동이 일어나게 된다.

하지만 ajax 의 경우 브라우저의 url 주소의 변경을 이용하지 않고 내부적으로 통신하여 response 를 받아오기 때문에 특정 데이터만 불러오거나 비동기로 데이터를 불러와야하는 경우 사용된다.

이때 Same Origin Policy 정책으로 인해 cross domain 을 허용하지 않기 때문에 외부 도메인을 사용하여야 하는경우 JSONP, XML 등을 이용하여야 한다.

12 . JSONP가 어떻게 동작 되는지 설명하세요.(그리고,실제 AJAX와 어떻게 다른지 설명하세요.)

앞서서 말했듯이 Ajax 는 크로스 도메인을 허용하지 않기 때문에 이를 해결하기 위하여 JSONP 를 이용한다. JSONP 는 GET 요청만이 가능하다.

JSONP 는 script 코드가 D.O.M 트리에 추가되면 실행 되어 지면 외부 스크립트를 로드 할수 있다는 것에서 착안된 것으로 동적으로 외부 스크립트를 호출하면서 callback 함수를 명시해 주면 스크립트가 실행된후 callback 함수를 실행한다는 로직으로 구성된어 있다.

13. "호이스팅(Hoisting)"에 대해서 설명 하시오.

호이스팅은 자바스크립트 엔진이 실행 컨텍스트를 생성하면서 scope 를 정의 할때 기술된 순서에 상관없이 선언부에 대한 처리를 해석 우선순위 최우선으로 끌어올려 먼저 해석하는 것을 의미한다.

싶게 말해서 코드 작성 순서에 상관없이 선언구문을 최우선으로 해석한다는 것이다.

foo='hello'
console.log(foo);
var foo;
> hello

이때 유념해야할 것이 하나 있는데 호이스팅은 선언에만 적용되고 할당구문에는 적용되지 않는다.

console.log(foo);
var foo='hello world';
>undefined

이처럼 선언과 동시에 값을 할당하는 경우 호이스팅 되지 않으며 해당구문을 만나야만 해석하게 된다.

14. FOUC가 무엇이며 FOUC를 어떻게 방지하나요?

FOUC(Flash Of Unstyled Content)는 외부의 CSS가 불러오기 전에 잠시 스타일이 적용되지 않은 웹 페이지가 나타나는 현상이다. 이를 해결하기 위해서 CSS 관련 로딩 구문은 반드시 head 안에 포함시켜 css 로드 전에 D.O.M 트리를 구성하는 것을 방지 주는 것이 좋다.

이게 왜 Javascript 영역에 있는지 잘모르겠다. 혹은 내가 잘 모르는 뭔가가 있는 듯하다.

15. 이벤트 버블링(Event Bubbling)에 대해서 설명하세요.

자바스크립트의 이벤트 전파는 다음과 같은 방식을 따른다.

  1. 이벤트 발생(사용자 이벤트 혹은 강제 이벤트 할당:trigger)
  2. 이벤트 발생 객체를 찾기 위하여 하위 탐색(캡쳐)
  3. 이벤트 발생 객체 도달
  4. 하위 탐색의 역순으로 복귀(버블링)

    IE의 경우 위와 같은 탐색에서 캡션단계를 지원하지 않음으로 이벤트 핸들링은 버블링을 기준으로 작성되어야 한다.

어찌 되었건 이벤트가 발생한 타겟에서 시작하여 상위로 해당 이벤트가 계속 해서 전파되어 버린다. 이를 버블링이라하며 이와 같은 버블링으로 인한 오작동을 방지 하기 위해서는 stopPropagation() 을 이용하여 이벤트 전파를 차단해 주어야 한다.

16. Javascript 객체를 확장하는 것이 좋지 않은 이유.

사실 이 문제도 제대로 이해하지 못했다. 그래서 이미 정이된 객체의 prototype을 특정영역에서 변경해 버리는 것에 대한 내용을 적는다.

Javascript의 객체를 Object.prototype 을 이용해서 확장하는 것은 좋은 방법이 아니다. 이는 기본적으로 참조 무결성을 깨틀여 버리게 된다 Object.prototype 을 확장하거나 변경하는 행위는 해당 prototype을 참조로 하는 모든 객체에 영향을 미치게 된다. 즉 특정 영역에서 변경을 위해 수행된 코드로 인해 애플리케이션 전체에 영향을 주게 된다는 것이다.

이는 극히 위험한 일이다. 상황에 따라 변형되어버리는 이런 구조로는 프로그램을 예측할수도 없을 뿐아니라 추후 코드 실행의 무결성을 보장하지 않는다.

17. Document Load 이벤트와 Ready 이벤트의 차이가 무엇인가요?

두 Event 모두 D.O.M 을 다루기 위한 이벤트 이다. 하지만 두 이벤트에는 큰차이가 있다.

Ready 이벤트의 경우 D.O.M 트리가 만들어지면 발생한다. 즉 리소스 다운로드 상태와는 상관없이 Element 구조를 형성하게 되면 발생하게 된다. 하지만 이때 사용자가 리소스 핸들링을 위한 이벤트를 발생하게 된다면 오류를 내뱉게 된다.

하지만 load 이벤트는 페이지에 정의된 모든 리소스의 다운로드가 완료될경우 발생하게 된다. load 이벤트 이후에는 어떤 리소스로의 접근도 가능하다.

18. ==와 ===의 차이점은 무엇인가요?

===는 typeof 를 포함 한다. 즉 단순히 값이 같다는 것외에도 데이터타입도 같이 검사하게 된다. javascript 에서는 아래와 같은 동작을 하게 됨으로 비교연산시 주의를 요한다.


if('3'==3) console.log('equal');
else console.log('not equal');
>equal;

if('3'===3) console.log('equal');
else console.log('not equal');
>not equal;

19. 브라우저의 URL에서 파라메터를 얻을 수 있는 방법에 대해서 설명하세요.

var params = document.location.split('?')를 이용하면 된다.

20. Javascript의 "동일출처정책(the same-origin policy)"에 대해서 설명하세

Javascript 는 스크립트가 사용자의 입력을 캐내어 다른 페이지로 흘려보내는 것을 막기 위한 보안정책으로 동일 출처 정책을 가지고 있다.

스크립트는 자신을 포함한 문서와 다른 서버에서 불러온 문서의 내용은 읽을 수 없다. 이와 유사하게 스크립트는 다른 서버에서 불러온 문서에는 이벤트 리스너를 등록할 수 없다.

21. 이벤트 딜리게이션(Event Delegation)에 대해서 설명하세요.

이벤트 딜리게이션은 이벤트가 발생되어야 하는 객체에 직접적으로 이벤트를 바인딩 시키는 것이 아닌 객체 상위 요소에 이벤트를 할당하고 인자로 evne 객체를 넘겨줌으로서 실제 이벤트 타겟에 간접적으로 이벤트 바인드 효과를 주는 것을 말한다.

이는 javascript의 이벤트 할당이 메모리에 직접적으로 올라가게 됨으로 반복적이고 과다한 이벤트 할당은 프로그램 성능적으로나 반복적인 코딩등에 문제로 많이 사용되는 이벤트 바인딩 패턴이다.

22. 다음 코드를 동작하게 만드세요. javascript [1,2,3,4,5].duplicator(); // [1,2,3,4,5,1,2,3,4,5]


Array.prototype.duplicator = function() {
   var $this = this;
   return $this.concat($this);  
};

console.log([1,2,3,4,5].duplicator() );
>[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

사실 문제가 너무 많아 대충 대충 적었다. 이문제들에 대한 각가의 내용을 블로그에 한섹션으로 다뤄도 될정도의 분량을 뽑아 낼수 있기에 대충 대충 적어놓았다고 변명 해 본다.


출저 : http://insanehong.kr/post/fuck-that-statup/  , Insanehong

Actual width and height in modern browsers

Modern browsers (including IE9) provide naturalWidth and naturalHeight properties to IMG elements. These properties contain the actual, non-modified width and height of the image.

var nWidth = document.getElementById('example').naturalWidth;
var nHeight = document.getElementById('example').naturalHeight;

Actual width and height in IE8 and IE7

The naturalWidth and naturalHeight properties are not supported in IE8 or lower. The height and width property will give the apparent width and height after stylesheet and inline styles, inline width and height attributes, and the display property of the image and the image's parent elements have been applied. The simplest way to get the unmodified width and height is to create a new image element, and use its width and height property.

function getNatural (DOMelement) {
  var img = new Image();
  img.src = DOMelement.src;
  return {width: img.width, height: img.height};
}

var natural = getNatural(document.getElementById('example')),
  nWidth = natural.width,
  nHeight = natural.height;

jQuery naturalWidth() and naturalHeight()

Here is a short jQuery (any version) plugin that adds two methods: naturalWidth() and naturalHeight(). It uses branching to determine if naturalWidth and naturalHeight are supported by the browser. If supported, the method just becomes a getter for the naturalWidth or naturalHeight property. If not supported, the method creates a new unstyled image element and returns that element's actual width and height.

// adds .naturalWidth() and .naturalHeight() methods to jQuery
// for retreaving a normalized naturalWidth and naturalHeight.
(function($){
  var
  props = ['Width', 'Height'],
  prop;

  while (prop = props.pop()) {
  (function (natural, prop) {
    $.fn[natural] = (natural in new Image()) ? 
    function () {
    return this[0][natural];
    } : 
    function () {
    var 
    node = this[0],
    img,
    value;

    if (node.tagName.toLowerCase() === 'img') {
      img = new Image();
      img.src = node.src,
      value = img[prop];
    }
    return value;
    };
  }('natural' + prop, prop.toLowerCase()));
  }
}(jQuery));

// Example usage:
var nWidth = $('img#example').naturalWidth();
var nHeight = $('img#example').naturalHeight();

Hey,

Follow me on Twitter and Github, that's where I'm most active these days. I welcome email (), but I'm afraid I no longer have time to answer personal requests for help regarding my plugins or posts. Thanks!

 

출저 : http://www.jacklmoore.com/notes/naturalwidth-and-naturalheight-in-ie/,

'자바스크립트' 카테고리의 다른 글

promise 강의  (0) 2017.05.10

팩토리 패턴 (factory pattern)


팩토리 메소드 패턴 : 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를

                                 만들지는 서브클래스에서 결정하게 만든다. 즉 팩토리 메소드 패턴을 이용하면

                                 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것.


추상 팩토리 패턴 : 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성.


new를 사용하는 것은 구상 클래스의 인스턴스를 만드는 것이다.

당연히! 인터페이스가 아닌 특정 구현을 사용하게 되어버리는 것.

일련의 구상 클래스들이 있을때는 어쩔수 없이 다음과 같은 코드를 만들어야 하는 경우가 있음.


 Duck duck;

 if ( type == picnic ) duck = new MallardDuck();

 else if ( type == hunting ) duck = new DecoyDuck();

 else if ( type == inBathTub) duck = new RubberDuck();


이런 코드가 있다는 것은, 뭔가 변경하거나 확장해야 할 때 코드를 다시 확인하고 추가 또는 제거해야 한다는 것을 의미함.


인터페이스에 맞춰서 코딩을 하면 시스템에서 일어날 수 있는 여러 변화를 이겨낼 수 있다. 왜냐하면..

다형성 덕분에 어떤 클래스든 특정 인터페이스만 구현하면 사용할수 있기 때문.

반대로. 구상 클래스를 많이 사용하면 새로운 구상 클래스가 추가될 때마다 코드를 고쳐야 하기때문에 많은 문제가 생길수 있다.

즉 변화에 대해 닫혀 있는 코드가 되어버리는 것.


디자인 원칙으로 봤을때 구상 클래스를 바탕으로 코딩을 하면 나중에 코드를 수정해야 할 가능성이 높아지고, 유연성이 떨어진다는걸 다시한번 확인했는데 그렇다면 회피할수 있는 방법은??

- 바뀔 수 있는 부분을 찾아내서 바뀌지 않는 부분하고 분리시켜야 한다는 원칙.




피자 가게를 운영하고 있고 피자가게 클래스를 만들어야 된다고 가정할때.



 Pizza orderPizza(String type) {

       Pizza pizza;


       if(type.equals("cheese")) pizza = new CheesePizza();

       else if(type.equals("greek")) pizza = new GreekPizza();

       else if(type.equals("pepperoni")) pizza = new PepperoniPizza();       


       pizza.prepare();

       pizza.bake();

       pizza.cut();

       pizza.box();

       return pizza;

 } 


굵게 표시된 부분이 매번 바뀌는 부분.. 글엄 객체 생성 부분을 캡슐화 해보자.

그리고 새로 만들 객체에는 팩토리(Factory) 라는 이름을 붙이기로 한다.


간단한 피자 팩토리를 만든다.


 public class SimplePizzaFactory {

public Pizza createPizza(String type){ //이런 경우에는 static메소드로 선언하는 경우가 종종 있음.

Pizza pizza = null;

if(pizza.equals("cheese")) pizza = new CheesePizza();

if(pizza.equals("pepper")) pizza = new PepperoniPizza();

if(pizza.equals("clam")) pizza = new ClamPizza();

if(pizza.equals("veggie")) pizza = new VeggiePizza();

return pizza;

}

 }



워밍업. 간단한 팩토리 (Simple Factory)

 

 public class PizzaStore{

SimplePizzaFactory simplePizzaFactory;

public PizzaStore(SimplePizzaFactory simplePizzaFactory) {

this.simplePizzaFactory = simplePizzaFactory;

}

public Pizza orderPizza(String type){

Pizza pizza;

pizza = simplePizzaFactory.createPizza(type);

pizza.prepare();

pizza.bake();

pizza.cut();

pizza.box();

return pizza;

}

 }


간단한 팩토리는 디자인 패턴이라고 할 수는 없다.

프로그래밍을 하는데 있어서 자주 쓰이는 관용구에 가깝다고 할 수 있다.





피자 가게가 사업이 확장되어 여러 지역별로 각각의 다른 스타일의 피자를 만들어 내야하는 문제가 생겼다.

모든 프랜차이즈 분점에서 PizzaStore 코드를 사용하여 진행을 한다.


간단한 팩토리를 사용한다면 ???

SimplePizzaFactory 를 빼고 세가지 서로 다른 팩토리 (NYPizzaFactory, ChicagoPizzaFactory, CalifornizPizzaFactory)를 만들어서 적용한다면?




 PizzaStore nyStore = new PizzaStore(new NYPizzaFactory());

 nyStore.order("cheese");

 

 PizzaStore chicagoStore = new PizzaStore(new ChicagoPizzafactory());

 chicagoStore.order("cheese");


이런식으로 구현을 해보니.. 각 팩토리를 가진 피자가게 체인점들이 서로의 구현방식이 달라지는 일이 발생할수도 있게 됬음. (PizzaStore이 각각 있다보니 굽는 방식이 달라진다거나 피자를 자르는 단계를 빼먹거나 하는..)




1. 팩토리 메소드 패턴


피자가게와 피자 제작 과정 전체를 하나로 묶어주는 프레임워크를 만들어야 된다는 결론!!

파자를 만드는 활동 자체는 전부 PizzaStore 클래스에 국한시키면서도 분점마다 고유의 스타일을 살리수 있는 방법은 ??



 public abstract class PizzaStore{

public Pizza orderPizza(String type){

Pizza pizza;

pizza = createPizza(type);

pizza.prepare();

pizza.bake();

pizza.cut();

pizza.box();

return pizza;

}

abstract Pizza createPizza(String type); //Pizza 인스턴스를 만드는 일은 팩토리 역할을 하는 메소드에서 맡아 처리

 }


이제 각 분점을 위한 지역별로 서브클래스를 만들어줘야 한다. 피자의 스타일은 각 서브클래스에서 결정.



이제 ChicagoPizzaStore, NYPizzaStore 에는구상 피자클래스를 분기해주는 각각의 createPizza 메소드가 있음.



 public class NYPizzaStore extends PizzaStore{

@Override

public Pizza createPizza(String type){

Pizza pizza = null;

if(type.equals("cheese")) pizza = new NYStyleCheesePizza();

if(type.equals("peper")) pizza = new NYStylePepperoniPizza();

if(type.equals("clam")) pizza = new NYStyleClamPizza();

if(type.equals("veggie")) pizza = new NYStyleVeggiePizza();

return pizza;

}

 } 



 public class ChicagoPizzaStore extends PizzaStore{

@Override

public Pizza createPizza(String type){

Pizza pizza = null;

if(type.equals("cheese")) pizza = new ChicagoStyleCheesePizza();

if(type.equals("peper")) pizza = new ChicagoStylePepperoniPizza();

if(type.equals("clam")) pizza = new ChicagoStyleClamPizza();

if(type.equals("veggie")) pizza = new ChicagoStyleVeggiePizza();

return pizza;

}

 } 




 public abstract class Pizza{
String name;
String dough;
String sauce;
ArrayList<String> toppings = new ArrayList<>();
public void prepare(){
System.out.println("Preparing : "+name);
System.out.println("Tossing dough...");
System.out.println("Adding source");
System.out.println("Adding toppings");
for (String topping : toppings) {
System.out.println("\ttopping : "+topping);
}
}
public void bake(){
System.out.println("Bake for 25 minutes at 350");
}
public void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
public void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public String getname(){
return this.name;
}
 }


 public class NYStyleCheesePizza extends Pizza{

public NYStyleCheesePizza() {

this.name = "NY Style CheesePizza";

this.dough = "Thin Crust Dough";

this.sauce = "Marinara Sauce";

this.toppings.add("Grated Reggiano Cheese");

}

 }



 public class ChicagoStyleCheesePizza extends Pizza{

public ChicagoStyleCheesePizza() {

this.name = "Chicago Style CheesePizza";

this.dough = "Extra Thick Crust Dough";

this.sauce = "Plum Tomato Sauce";

this.toppings.add("Shredded mozzarella Cheese");

}

@Override

public void cut() {

System.out.println("Cutting the pizza into square slices");

}

 }

 


 public class PizzaTestDrive {

public static void main(String[] args) {

PizzaStore nyStore = new NYPizzaStore();

PizzaStore chicagoStore = new ChicagoPizzaStore();

Pizza nySytpePizza = nyStore.orderPizza("cheese");

System.out.println(nySytpePizza.getname());

System.out.println();

Pizza chicagoStypePizza = chicagoStore.orderPizza("cheese");

System.out.println(chicagoStypePizza.getname());

}

 } 



모든 팩토리 패턴에서는 객체 생성을 캡슐화 한다.

팩토리 메소드 패턴에서는 서브 클래스에서 어떤 클래스를 만들지를 결정하게 함으로써 객체 생성을 캡슐화 한다.



객체를 생산하는 생산자 클래스




제품을 생산하는 제품 클래스



위의 클래스 아이어 그램을 보면 생산을 담당하는 PizzaStore 추상 클래스에서 객체를 만들기 위한 메소드, 즉 팩토리 메소드를 위한 인터페이스를 제공한다는 것을 알수있다. PizzaStore에 구현되어 있는 다른 메소드 orderPizza 에서는 팩토리 메소드에 의해 생산된 제품을 가지고 필요한 작업을 처리한다. 하지만 실제 팩토리 메소드를 구현하고 제품(객체 인스턴스)을 만들어 내는 일은 서브클래스에서만 할수 있다.




이로써 구상 클래스에 대한 의존성을 줄이는 것이 좋다는 것은 이제 확실해졌다.

이런 내을 정리해  놓은 객체지향 디자인 원칙이 바로 의존성 뒤집기 원칙 (Dependency Inversion Principle) 이다.


디자인 원칙


추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다.


왜 의존성 뒤집기 원칙이냐면...


PizzaStore -> NYStyleCheesePizza

PizzaStore -> ChicagoStypeCheesePizza

PizzaStore -> NYStyleVeggiePizza


이런식으로 의존이 되던 좋지않은 디자인이


PizzaStore -> Pizza

Pizza <- NYStyleCheesePizza

Pizza <- ChicagoStyleCheesePizza

Pizza <- NYStyleVeggiePizza


이런식으로 의존관계게 뒤집어지는 형상.


팩토리 메소드 패턴을 적용하고 나면 고수준 구성요소(PizzaStore)와 저수준 구성요소(NYStyleCheesePizza, ChicagoStylePizza, ..) 들이 모두 추상 클래스인 Pizza에 의존하게됨. (고수준 모듈과 저수준 모듈이 둘다 하나의 추상 클래스에 의존)

팩토리 메소드 패턴이 의존성 뒤집기 원칙을 준수하기 위해 쓸 수 있는 유일한 기법은 아니지만 가장 적합한 벙법 가운데 하나. 



의존성 뒤집기 원칙에 위배되는 객체지향 디자인을 피하는데 도움이 되는 가이드.


 1. 어떤 변수에도 구상 클래스에 대한 레퍼런스를 지정하지 않는다.

     - new 연산자를 사용하면 레퍼런스를 사용하게 되는 것.


 2. 구상 클래스에서 유도된 클래스를 만들지 않는다.

     - 구상클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게됨, 추상화 된것을 사용해야 함.


 3. 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 않는다.

     - 이미 구현되어 있는 메소드를 오버라이드 한다는 것은 애초부터 베이스 클래스가 제대로 추상화 된것이 아니었다고

        볼수있다. 베이스 클래스에서 메소드를 정의할 때는 모든 서브 클래스에서 공유할 수 있는 것만 정의해야함.



위의 가이드 라인은 다른 원칙들과 마찬가지로 항상 지켜야 하는 규칙이 아니라. 지향해야하는 바를 밝히는 것.

String 인스턴스는 사실 별생각 없이 쓰는데 엄밀히 말하자면 이것도 원칙에 위배되는 것이지만 별문제가 되지않는다. 왜냐하면 String 클래스가 바뀌는 일의 거의 없을테니까. 하지만 자신이 만들고 있는 클래스가 바뀔 가능성이 있다면 팩토리 메소드 패턴 같은 기법을 써서 변경될 수 있는 부분을 캡슐화 하여야 한다.



이렇게 PizzaStore 디자인이 모양새를 갖췄다. 유연한 프레임워크도 만들어 졌고, 디자인 원칙도 충실하게 지켰다.

각각 체인점들이 미리 정해놓은 절차를 잘 따르고 있지만 몇몇 체인점들이 자잘한 재료를 더 싼 재료로 바꿔서 원가를 절감해 마진을 남기고 있다. 원재료의 품질까지 관리하는 방법이 있을까??

     - 원재료 군을 만들어 파악하자.

       제품에 들어가는 재료군(반죽, 소스, 치즈, 야채, 고기)은 같지만, 지역마다 재료의 구체적인 내용이 조금씩 다르다.



2. 추상 팩토리 패턴



원재료 공장을 만들어보자.


1. 지역별로 팩토리를 만들어 각 생성 메소드를 구현하는 PizzaingredientFactory 클래스를 만들어야 함.

2. ReggianoCheese, RedPeppers, ThickCrustDough와 같이 팩ㄴ토리에서 사용할 원재료 클래스들을 구현한다.

3. 만든 원재료 공장을 PizzaStore 코드에서 사용하도록 함으로써 모든 것을 하나로 묶어준다. 



 public interface PizzaIngredientFactory {

public Dough createDough();

public Sauce createSauce();

public Cheese createCheese();

public Veggies[] createVeggies();

public Pepperoni createPepperoni();

public Clams createClams();

 } 




 public class NYPizzaingredientFactory implements PizzaIngredientFactory{

@Override

public Dough createDough() {

return new ThinCrustdough();

}


@Override

public Sauce createSauce() {

return new MarinaraSauce();

}


@Override

public Cheese createCheese() {

return new ReggianoCheese();

}


@Override

public Veggies[] createVeggies() {

Veggies veggies[] = { new Farlic(), new Onion(), new Mushroom(), new RedPepper() };

return veggies;

}


@Override

public Pepperoni createPepperoni() {

return new SlicedPepperoni();

}


@Override

public Clams createClams() {

return new Freshclams();

}

 }



 public class ChicagoPizzaingredientFactory implements PizzaIngredientFactory{
@Override
public Dough createDough() {
return new ThickCrustDough();
}

@Override
public Sauce createSauce() {
return new PlumTomatoSauce();
}

@Override
public Cheese createCheese() {
return new MozzarellaCheese();
}

@Override
public Veggies[] createVeggies() {
Veggies veggies[] = { new BlackOlives(), new Spinach(), new EggPlant()};
return veggies;
}

@Override
public Pepperoni createPepperoni() {
return new Slicedpepperoni();
}

@Override
public Clams createClams() {
return new FrozenClam();
}
 }
 

 public abstract class Pizza{
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
public abstract void prepare(); //추상 메소드로 변경됨.
public void bake(){
System.out.println("Bake for 25 minutes at 350");
}
public void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
public void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public String getname(){
return this.name;
}
 } 



 


 public class CheesePizza extends Pizza{

PizzaIngredientFactory ingredientFactory;

public CheesePizza(PizzaIngredientFactory ingredientFactory) {

this.ingredientFactory = ingredientFactory;

}


@Override

public void prepare() {

this.dough = ingredientFactory.createDough();

this.sauce = ingredientFactory.createSauce();

this.cheese = ingredientFactory.createCheese();

}

 } 




 public class ClamPizza extends Pizza{

PizzaIngredientFactory ingredientFactory;

public ClamPizza(PizzaIngredientFactory ingredientFactory) {

this.ingredientFactory = ingredientFactory;

}


@Override

public void prepare() {

this.dough = ingredientFactory.createDough();

this.sauce = ingredientFactory.createSauce();

this.cheese = ingredientFactory.createCheese();

this.clams = ingredientFactory.createClams();

}

 }



 public class NYPizzaStore extends PizzaStore{

@Override

public Pizza createPizza(String type){

Pizza pizza = null;

PizzaIngredientFactory ingredientFactory = new NYPizzaingredientFactory();

if(type.equals("cheese")){

pizza = new CheesePizza(ingredientFactory);

pizza.setName(ingredientFactory.NY_STYLE+" Cheese Pizza");

}else if(type.equals("peper")){

pizza = new PepperoniPizza(ingredientFactory);

pizza.setName(ingredientFactory.NY_STYLE+" Pepperoni Pizza");

}else if(type.equals("clam")){

pizza = new ClamPizza(ingredientFactory);

pizza.setName(ingredientFactory.NY_STYLE+" Clam Pizza");

}else if(type.equals("veggie")){

pizza = new VeggiePizza(ingredientFactory);

pizza.setName(ingredientFactory.NY_STYLE+" Veggie Pizza");

}

return pizza;

}

 } 






이제 전체적인 흐름은.


1. 뉴욕 피자가게를 만든다.

     - PizzaStore nyPizzaStore = new NYPizzaStore();


2. 주문을 한다.

     - nyPizzaStore.orderPizza("cheese");


3. orderPizza 메소드에서는 우선 createPizza() 메소드를 호출한다

     - Pizza pizza = createPizza("cheese");


4. createPizza() 메소드가 호출되면 원재료 공장이 돌아가기 시작한다.

     - Pizza pizza = new CheesePizza(nyIngredientFactory);


5. 피자를 준비하는 prepare()메소드가 호출되면 팩토리에 원재료 주문이 들어간다.

     - void prepare(){

            dough = nyIngredientFactory.createDough();

            sauce = nyIngredientFactory.createSauce();

            cheese = nyIngredientFactory.createCheese();

       }


6. 준비단계가 끝나고 orderPizza() 메소드에서는 피자를 굽고, 자르고, 포장한다.





다시 한번 정리를 해보면..



 추상 팩토리 패턴 : 제품군을 생성하기 위한 인터페이스를 생성 그 인터페이스를 구성하여 사용할수 있게끔 하는것.


 추상 메소드 패턴 : 하나의 추상클래스에서 추상 메소드를 만들고 서브클래스들이

                          그 추상메소드를 구현하여 인스턴스를 만들게끔 하는것.



출처: http://jusungpark.tistory.com/14 [정리정리정리]

+ Recent posts