티스토리 뷰

웹개발/Php

SOILD 원칙 공부하기

yaku 2016. 1. 6. 17:49


//S SRP 단일 책임 원칙 (Single responsibility principle) : 한 클래스는 하나의 책임만 가져야 한다.
//O OCP 개방-폐쇄 원칙 (Open/closed principle) : “소프트웨어 요소는...확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
//L LSP 리스코프 치환 원칙 (Liskov substitution principle) : “프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라.
//I ISP 인터페이스 분리 원칙 (Interface segregation principle) : “특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.”
//D DIP 의존관계 역전 원칙 (Dependency inversion principle) : 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안 된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.

class Prime
{
    public function main()
    {
        $nums = array(1,2,3,5,6,7,8,9,10,11,12,13,14,15);
        print_r( $nums );
        echo "
"; echo "Evens"; print_r( $this->getEvents( $nums ) ); echo "
"; echo "Primes"; print_r( $this->getPrimes( $nums ) ); } public function getEvents( $nums ) { $result = array(); foreach ( $nums as $n ) { if ( $n % 2 == 0 ) { array_push($result, $n ); } } return $result; } public function getPrimes( $nums ) { $result = array(); $iCnt = count( $nums ); foreach ( $nums as $n ) { $isPrime = true; for ( $i = 2; $i < $n ; $i++ ) { if ( $n % $i == 0 ) { // 5 % 2, 5 % 3, 5 %4 $isPrime = false; break; } } if ( $isPrime ) { array_push( $result, $n ); } } return $result; } } $Prime = new Prime(); $Prime->main(); //책임 분리를 위해 가장 먼저 수행할 리팩토링(Refactoring) 작업은 책임 단위로 함수를 분리(Extract Method)하는 것이다. //세 가지 책임 중에서 우선 ‘판단 알고리즘’을 GetEvens와 GetPrimes 함수로부터 분리하면 //짝수 판단 알고리즘은 IsEven 함수에, 소수 판단 알고리즘은 IsPrime 함수로 분리할 수 있다. class Reprime { public static function getEvens( $nums ) { $result = array(); if ( empty($nums) === false ) { foreach ( $nums as $n ) { if ( self::isEvens($n) ) { array_push( $result, $n ); } } } echo "
"; print_r( $result ); } public static function isEvens( $number ) { return ( $number % 2 ) == 0; } public static function getPrime( $nums ) { $result = array(); if ( empty($nums) === false ) { foreach ( $nums as $n ) { if ( self::isPrime($n) ) { array_push( $result, $n ); } } } echo "
"; print_r( $result ); } public static function isPrime( $number ) { for ( $i= 2; $i < $number ; $i++ ) { if ( $number % $i == 0 ) { return false; } } return true; } } $nums = array(1,2,3,5,6,7,8,9,10,11,12,13,14,15); Reprime::getEvens( $nums ); Reprime::getPrime( $nums ); //위을 통해 판단 알고리즘은 개별 함수(IsEven, IsPrime)로 분리 됐지만, 여전히 GetEvens과 GetPrimes 함수에 의존하고 있다. //의존성으로 인해 아직까지 완전히 책임이 분리되지 않는 것을 확인할 수 있다. //의존관계 역전 원칙 //GetEvens과 GetPrimes에서 판단 알고리즘인 IsEven과 IsPrime의 의존성 제거를 위한 대표적인 방법은 인터페이스를 이용하는 것이다. //인터페이스는 제공할 서비스의 메소드 시그니처(Signature)만을 갖고 있기 때문에 인터페이스를 사용하는 곳에서는 구체적인 구현 내용을 알 수 없다. //GetEvens와 GetPrimes에서 판단 알고리즘을 인터페이스로 분리하면 짝수와 소수를 판단하는 구체적인 알고리즘으로부터 분리할 수 있게된다. //은 판단 알고리즘의 구체적인 구현 내용을 은닉시키기 위한 IPredicate 인터페이스를 정의한다. //그리고 정의된 인터페이스로부터 상속을 받은 클래스에 짝수와 소수 판단 알고리즘을 각각 구현한다. interface IPredicate { public function predicate( $number ); } class DipEven implements IPredicate { public function predicate( $number ) { return ( $number % 2 ) == 0; } } class DipPrime implements IPredicate { public function predicate( $number ) { for ( $i= 2; $i < $number ; $i++ ) { if ( $number % $i == 0 ) { return false; } } return true; } } //판단 알고리즘이 인터페이스로 분리됐기 때문에 GetEvens와 GetPrimes 함수에서는 아래와 같이 해당 인터페이스를 메소드 인자로 전달 받도록 변경한다. class DipPrimeClass { public static function getEvens( $nums , $predicate ) { $result = array(); if ( empty($nums) === false ) { foreach ( $nums as $n ) { if ( $predicate->predicate($n) ) { array_push( $result, $n ); } } } echo "
"; print_r( $result ); } public static function getPrime( $nums, $predicate ) { $result = array(); if ( empty($nums) === false ) { foreach ( $nums as $n ) { if ( $predicate->predicate($n) ) { array_push( $result, $n ); } } } echo "
"; print_r( $result ); } } $nums = array(1,2,3,5,6,7,8,9,10,11,12,13,14,15); DipPrimeClass::getEvens( $nums, new DipEven() ); DipPrimeClass::getPrime( $nums, new DipPrime() ); //개방-폐쇄 원칙 // 판단 알고리즘 사용자(GetEvens와 GetPrimes)와 판단 알고리즘(IsEven과 IsPrime)의 책임이 인터페이스로 분리됐다. // 그리고 인터페이스로 서로가 의존하기 때문에 판단 알고리즘 사용자는 판단 알고리즘에 구체적인 내용을 알 수 없다.책임이 클래스 단위로 분리(단일 책임 원칙)돼 있으며, 분리된 클래스는 구체적인 클래스가 아닌 인터페이스 단위로 의존(의존관계 역전 원칙)하고 있기 때문이다. //단일 책임 원칙과 의존관계 역전 원칙을 준수한 결과 특정 판단 알고리즘에 개선이 필요하다면 판단 알고리즘 단위로 개선 작업을 수행할 수 있다. //추가로 판단 알고리즘이 필요하다면 판단 알고리즘 사용자(GetEvens와 GetPrimes)에 대한 변경 없이(Closed) IPredicate 인터페이스를 상속 받은 클래스만 만들면 새로운 기능이 추가(Open)된다. //아래 에서는 분리된 판단 알고리즘을 주입하는 과정을 확인할 수 있다. //‘단일 책임 원칙’, ‘의존관계 역전 원칙’, ‘개방-폐쇄 원칙’을 통해 수정한 GetEvens와 GetPrimes 함수를 확인해 보면 <그림 3>과 같이 함수 이름만 제외하고 100% 동일한 코드인 것을 확인할 수 있다. class OcpPrime { public function main() { $nums = array(1,2,3,5,6,7,8,9,10,11,12,13,14,15,16,17); echo "
"; echo "Ocp Evens"; print_r( $this->filter( $nums, new DipEven() ) ); echo "
"; echo "Ocp Primes"; print_r( $this->filter( $nums, new DipPrime() ) ); } // 이전까지 개별 단위로 구현돼어 재사용이 없던 GetEvens와 GetPrimes 함수가 <리스트 7>과 같이 구체적인 판단 알고리즘에 독립된 Filter 함수를 재사용할 수 있게 됐다. public function filter( $nums, $predicate ) { $result = array(); if ( empty($nums) === false ) { foreach ( $nums as $n ) { if ( $predicate->predicate($n) ) { array_push( $result, $n ); } } } return $result; } } $ocpPrime = new OcpPrime(); $ocpPrime->main(); //데이터 접근 책임 분리 //지금까지 판단 알고리즘 책임을 분리시켰다. 그럼 판단 알고리즘 분리와 동일한 방법으로 데이터 접근 책임도 분리해 보자. //판단 알고리즘을 분리하기 위해 명시적으로 IPredicate 정의했지만, 데이터 접근을 위한 인터페이스는 이미 PHP에서 이터레이터 인터페이스로 제공하고 있다. // IEnumerable 인터페이스는 foreach와 함께 내부적으로 데이터 접근 방법을 은닉하게 된다. class tIterator_array implements Iterator { private $myArray; public function __construct( $givenArray ) { $this->myArray = $givenArray; } function rewind() { return reset($this->myArray); } function current() { return current($this->myArray); } function key() { return key($this->myArray); } function next() { return next($this->myArray); } function valid() { return key($this->myArray) !== null; } } class AccessPrime { public function main() { $nums = array(1,2,3,5,6,7,8,9,10,11,12,13,14,15,16,17); echo "
"; echo "Access Evens"; print_r( $this->filter( $this->orderByDescending($nums), new DipEven() ) ); echo "
"; echo "Access Primes"; print_r( $this->filter( $this->orderByDescending($nums), new DipPrime() ) ); } // 이전까지 개별 단위로 구현돼어 재사용이 없던 GetEvens와 GetPrimes 함수가 <리스트 7>과 같이 구체적인 판단 알고리즘에 독립된 Filter 함수를 재사용할 수 있게 됐다. public function filter( $nums, $predicate ) { $result = array(); if ( empty($nums) === false ) { foreach ( $nums as $n ) { if ( $predicate->predicate($n) ) { array_push( $result, $n ); } } } return $result; } public function orderByDescending( $nums) { //$array = array('Alpha', 'atomic', 'Beta', 'bank'); //$array_lowercase = array_map('strtolower', $array); //array_multisort($array_lowercase, SORT_ASC, SORT_STRING, $array); //bool array_multisort ( array &$array1 [, mixed $array1_sort_order = SORT_ASC [, mixed $array1_sort_flags = SORT_REGULAR [, mixed $... ]]] ) array_multisort($nums, SORT_DESC); return $nums; } } $acessPrime = new AccessPrime(); $acessPrime->main(); //고차 함수 //지금까지는 객체지향 프로그래밍 입장에서 책임을 인터페이스 기준으로 분리시켜 책임 단위로 기능을 조합할 수 있음을 알아봤다. //이를 통해 책임이 클래스 단위로 캡슐화돼 있으며, 캡슐화된 책임이 메소드 인자로 전달되고 있는 것을 확인했다. //함수형 프로그래밍에서는 클래스 단위가 아닌 함수 단위로 책임이 캡슐화되며, 캡슐화된 함수가 메소드 인자로 전달될 수 있다. //특히 캡슐화된 함수를 인자로 받거나 결과를 내보낼 수 있는 함수는 고차 함수(Higher-Order Function 또는 고계 함수)라고 한다. ///함수형 프로그래밍에서는 함수 단위로 캡슐화할 수 있기 때문에 인터페이스와 클래스 없이 함수만으로 책임을 분리하고 의존성을 제거함으로써 확장에는 열려 있으나 변경에는 닫히도록 설계할 수 있다. 객체지향보다 불필요한 코드를 최소화해 문제의 본질에 집중할 수 있는 간결한 코드를 구현할 수 있는 것이다. //.NET 프레임워크 3.5부터 결과 값이 있는 메소드를 캡슐화하기 위한 Func을 제공하고 있다. <리스트 10>에서 사용한 Func은 메소드 입력 인자 타입이 int형이며, 결과 타입이 bool인 함수를 캡슐화한다. Func을 이용한 Filter 함수는 입력 타입이 int형이며 결과 타입이 bool인 함수를 인자로 받을 수 있는 고차 함수가 됐다. Func는 구체적인 메소드 구현 내용이 없기 때문에 인터페이스 역할을 수행하게 된다. //Filter 함수가 고차 함수로 변경됐기 때문에 더 이상 인터페이스와 클래스 구현 없이 함수만으로 책임을 분리해 전달할 수 있게 됐다.

'웹개발 > Php' 카테고리의 다른 글

젠킨스 php 테스팅 셋팅하기  (0) 2016.01.19
composer 설치하기 ( window )  (0) 2016.01.12
XDEBUG + WINcACHEgRIND 활용하기  (0) 2015.11.23
.htaccess 연구하기  (0) 2015.06.16
코드이그나이터 show_404 에러 오버라이딩  (0) 2015.05.16
댓글
댓글쓰기 폼