티스토리 뷰
//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 |
댓글