카테고리 보관물: OOAD

OOP 설계 5원칙

OOP 기반의 소프트웨어를 설계하는데 지켜야 하는 OOP 설계 원칙에 대한 내용이고, 5개가 있다.

1. 단일 책임의 원칙(SRP : Single Responsibility Principle)

– 한 객체는 한 가지 책임을 가져야 한다는 원칙이다. 이 원칙은 응집도를 높이고, 결합도를 낮추게 한다.

2. 의존 관계 역전의 법칙(DIP : Dependency Inversion Principle)

– 클라이언트는 상세 클래스가 아닌 추상화(인터페이스, 추상클래스) 레이어에 의존해야 한다는 원칙이다. 확장 이슈가 있는 부분(공통된 특성 및 기능)은 추상화를 해야 한다는 것이다.

3. 인터페이스 분리의 원칙(ISP : Interface Segregation Principle)

– 클라이언트에 특화된 여러개의 인터페이스가 하나의 범용 인터페이스보다 좋다는 것이다.

4. 리스코프 대체 원칙(LSP : Liskov Substitution Principle)

– 상위 클래스는 파생클래스로 대체 가능해야 되는 원칙이다. 그래서, 상위 클래스의 기능은 파생클래스가 포함해야 한다는 것이다. 따라서, 파생클래스는 상위클래스보다 더 많은 기능을 제공한다. 아래의 lsp.doc 파일은 전에 스터디를 하면서, C#으로 만들어 본 예제이다.

lsp.doc

5. 개방 폐쇄 원칙(Open-Closed Principle)

– 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙이다. 이 원칙은 기존 클래스를 수정하지 말고, 상속이나 구현으로 기능을 확장해야 한다는 것이다.

싱글톤 패턴(Singleton Pattern)

아래는 싱글톤(Singleton) 패턴을 구현하는 몇 가지 방법이고, 개별 방법이 가지는 문제에 대해서 살펴보자.

1. EagerSingleton

static class EagerSingleton extends Singleton { 
    static final EagerSingleton theInstance = new EagerSingleton(); 
    static EagerSingleton getInstance() { 
        return theInstance; 
    } 
}

이 경우는 미리 싱글톤 인스턴스를 생성하는 방법으로 static final 필드에 인스턴스를 생성하여 할당하는 방법이다. 이 필드는 클래스로더에 의해서 EagerSingleton이 메모리로 올라오는 순간에 안전하게(thread-safe하게)초기화된다. 이 방법이 성능이 가장 좋을 것이다.
– 장점
동기화부담이 적다는 것이다. 클래스로더가 클래스를 로드하는 시점에 로드되서 안전하다.
– 단점
인스턴스를 미리 생성한다는 것이다.

2. SynchedSingleton

static class SynchedSingleton extends Singleton {
  static SynchedSingleton theInstance;
  static synchronized SynchedSingleton getInstance() {
    if (theInstance == null) {
      theInstance = new SynchedSingleton();
    }
    
    return theInstance;
  }
}

이 방법은 싱글톤 객체에 접근하는 메서드를 동기화하는 방법이다. 매번 동기화를 하기에 자바 메모리 모델에 따른 각종 문제를 방지할 수 있어서 스레드에 안전(thread-safe)하다.
– 단점
매번 동기화를 수행해서 수행 비용이 높다.

3. ThreadLocalSingleton

static class ThreadLocalSingleton extends Singleton {
  static final ThreadLocal perThreadInstance = new ThreadLocal();
  static final Object lock = new Object();
  static ThreadLocalSingleton theInstance;
  static ThreadLocalSingleton getInstance() {
    ThreadLocalSingleton instance = (ThreadLocalSingleton)(perThreadInstance.get());
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        if (instance == null) {
          instance = theInstance = new ThreadLocalSingleton();
        }
        
      } // copy global to per-thread
      perThreadInstance.set(instance);
    }
    return instance;
  }
}

이 방법은 Thread Local Storage를 사용한다. 자바 스레드 메모리 모델에서 문제가 발생하는 것은 멀티 CPU상황에서 각각의 CPU가 자신의 캐시내에 클래스의 필드를 복사해넣는다는 것입니다. 이런 캐시는 메인메모리와 동기화가 되어 있지 않다. 즉, 각각의 CPU가 하나의 클래스를 접근하게되면 각각의 CPU는 동일 클래스에서 다른 값을 가져올 수 있게된다. 싱글톤을 구현하면 instance라는 필드를 여러개의 CPU가 참조하는데 이 필드를 여러 CPU가 다른 값으로 처리한다는 것이다. 예를들면, CPU A가 인스턴스를 생성하고 생성한 인스턴스를 instance에 할당한다고 해 보자. 다음으로 CPU A가 또다시 이 instance 필드를 참조할 때는 생성된 인스턴스를 보게 된다. 그러나 CPU B가 이 instance필드를 참조할때는 instance필드가 null로 보여질 수 있다. 그 이유는 CPU A가 수행한 작업은 synchronized블록을 통과할때까지 메인 메모리에 반영이 안되고 자신의 캐시에만 담겨 있을 수 있고, CPU B역시 synchornized블록을 통과하지 않으면 메인메모리가 아닌 자신의 캐시만 확인하기 때문이다.

이를 해결하려면 개별 CPU가 동기화 블록을 진입하고 나와야 하는데, 이를 구현한 것이 위 코드입니다. 각각의 Thread는 자신만의 메모리공간으로 TLS(Thread Local Storage)를 가지고 있고, TLS는 개별 스레드가 가지는 공간으로 동기화가 필요 없다. 따라서 이 저장소에 해당 스레드가 synchronized블록을 한번이라도 다녀왔는지(한번이라도 다녀오면 CPU가 메인메모리의 값을 가져온다)를 저장한다.

4. SimulatedThreadLocalSingleton

static class SimulatedThreadLocalSingleton extends Singleton {
  static SimulatedThreadLocalSingleton theInstance;
  static final Object lock = new Object();
  static final Object key = new Object();
  static Singleton getInstance() {
    TSS t = (TSS)(Thread.currentThread());
    Singleton instance = (Singleton)(t.threadLocalHashtable.get(key));
    
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        if (instance == null)
          instance = theInstance = new SimulatedThreadLocalSingleton();
      } // copy global to per-thread
      t.threadLocalHashtable.put(key, instance);
    }
    return instance;
  }
}

이 방법은 ThreadLocal 클래스를 쓰지 않고 TLS를 직접 구현한 방식입니다.

5. VolatileSingleton

static class VolatileSingleton extends Singleton {
  static final Object lock = new Object();
  static volatile VolatileSingleton theInstance;
  static VolatileSingleton getInstance() {
    VolatileSingleton instance = theInstance;
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        if (instance == null) {
          instance = theInstance = new VolatileSingleton();
        }
      }
    }
    return instance;
  }
}

주의!)이 방법은 사용하면 안 된다.

이 방법은 volatile를 사용한다. volatile로 선언된 필드는 매번 원자적으로 스레드 안전하게 동작한다. 즉 개별 변수에 대한 접근이 매번 메인메모리와 동기화 되기에 스레드 안전하게 동작한다. synchronized와 volatile은 이처럼 변수의 접근마다 동기화를 하느냐 아니면 특정 블록을 통채로 동기화 하는냐의 문제에 대한 접근방법이다. 그러나 아쉽게도 volatile은 대부분의 자바 컴파일러에서 제대로 구현되어있지않으며 따라서 사용하는것을 권하지 않는다.

volatile은 동기화의 문제를 비롯한 다양한 암시적 작동이 보장되어야하는데 이를 제대로 책임지고 않기 때문이다.

6. DirectThreadFieldSingleton

static class DirectThreadFieldSingleton extends Singleton {
  static DirectThreadFieldSingleton theInstance;
  static final Object lock = new Object();
  static Singleton getInstance(TSS t) {
  Singleton instance = t.singleton;
  
  if (instance == null) {
    synchronized(lock) {
      instance = theInstance;
      if (instance == null) {
        instance = theInstance = new DirectThreadFieldSingleton();
      }
    } // copy global to per-thread
    t.singleton = instance;
  }
  
  return instance;
  }
}

인스턴스를 할당받고자하는 쪽에서 TSS라는 형태의 클래스를 스레드마다 할당한채로 갖고 있다가 이것을 싱글톤 클래스에 넘깁니다. 그러면 TSS.singleton변수의 값을가지고 동기화 수행여부를 결정하는 방식이다. ThreadLocal의 변형이며, 모든 인스턴스를 획득하고자하는 스레드가 TSS를 넘겨야한다는 점에서 좋은 방법은 아니다.

7. ThreadFieldSingleton

static class ThreadFieldSingleton extends Singleton {
  static final Object lock = new Object();
  static ThreadFieldSingleton theInstance;
  static Singleton getInstance() {
    TSS t = (TSS)(Thread.currentThread());
    Singleton instance = t.singleton;
    if (instance == null) {
      synchronized(lock) {
        instance = theInstance;
        
        if (instance == null) {
          instance = theInstance = new ThreadFieldSingleton();
        }
      } // copy global to per-thread
      
      t.singleton = instance;
    }

    return instance;
  }
}

이 방법 또한 ThreadLocal 구현에 대한 변형된 구현입니다. ThreadLocal 대신, 특정 싱글톤 클래스에 대한 인스턴스를 획득하려고 시도하는 스레드를 캐스팅해서 ThreadLocal을 구현한 것이다. 개인적으로 이 방법도 좋지 않다고 본다. 특정 클래스의 인스턴스를 획득하는데 스레드가 특정 인스턴스를 구현하고 있는지는 쉽게 가정하기 어렵기 때문이다.

Null Object Pattern

Null Object Pattern 또는 Null Object라고 하는 것은 NullReference를 방지하기 위해서 더미(?) 객체를 미리 만들어 넣고 기능을 제공하는 핸들러에 바인딩 되어 있는 타입의 객체(실 기능 구현 객체)가 널인지를 체크해서 NullPointerException을 방지하기 위한 패턴이다. 마틴 파울러님의 Introduce Null Object에서도 동일한 내용이 나온다.

글로 설명하는 것보다, 코드로 보면 명확하게 알 수 있다. 이 예제에서 핵심은 아래의 코드이다.

/**
* return logger
* @return
*/

public ILogger getLogger() {
  if(this.logger == null)
    return NullLogger.getInstance();

  return this.logger;
}

아래는 로그를 남기는 예제코드입니다.

package net.sjava.patterns.nullobject;
/**
*
* @author <a href="mailto:mcsong@gmail.com">mcsong@gmail.com</a>
* @since 2009. 9. 17.
*/

public interface ILogger {
/**
*
* @param log
*/
public void log(String log);
}

package net.sjava.patterns.nullobject;
/**
*
* @author <a href="mailto:mcsong@gmail.com">mcsong@gmail.com</a>
* @since 2009. 9. 17.
*/
public class FileLogger implements ILogger {

  @Override
  public void log(String log) {
    System.out.println("file log - " + log);
  }
}
package net.sjava.patterns.nullobject;
/**
*
* @author <a href="mailto:mcsong@gmail.com">mcsong@gmail.com</a>
* @since 2009. 9. 17.
*/
public class NullLogger implements ILogger {
  /**
  * singleton instance
  */
  private static final NullLogger instance = new NullLogger();
  /**
  *
  */
  private NullLogger() {
  }

  /**
  * return singleton instance
  * @return
  */
  public static NullLogger getInstance() {
    return NullLogger.instance;
  }

  @Override
  public void log(String log) {
    System.out.println("null log - " + log);
  }
}

</div>


<pre class="brush: java; gutter: true; auto-links: false">
package net.sjava.patterns.nullobject;
/**
*
* @author <a href="mailto:mcsong@gmail.com">mcsong@gmail.com</a>
* @since 2009. 9. 17.
*/
public class SocketLogger implements ILogger {

  @Override
  public void log(String log) {
    System.out.println("socket log - " + log);
  }
}
package net.sjava.patterns.nullobject;
/**
*
* @author <a href="mailto:mcsong@gmail.com">mcsong@gmail.com</a>
* @since 2009. 9. 17.
*/
public class LoggerHandler {

  /**
  * type
  */
  private ILogger logger;

  /**
  * constructor
  * @param logger
  */
  public LoggerHandler(ILogger logger) {
    this.logger = logger;
  }

  /**
  * return logger
  * @return
  */
  public ILogger getLogger() {
    if(this.logger == null)
      return NullLogger.getInstance();
    
    return this.logger;
  }

  /**
  *
  * @param log
  */
  public void log(String log) {
    this.getLogger().log(log);
  }

  /**
  *
  * @param logger
  * @param log
  */
  public void log(ILogger logger, String log) {
    this.logger = logger;
    this.log(log);
  }
}
import net.sjava.patterns.nullobject.FileLogger;
import net.sjava.patterns.nullobject.SocketLogger;
import net.sjava.patterns.nullobject.LoggerHandler;public class NullObjectTest {
  /**
  * handler
  */
  private static LoggerHandler handler;

  /**
  * @param args
  */
  public static void main(String[] args) {
    handler = new LoggerHandler(null);
    handler.log("fileloggger");
    handler = new LoggerHandler(new FileLogger());
    handler.log("socketloggger");
    handler = new LoggerHandler(new SocketLogger());
    handler.log("socketloggger");
  }
}

160BCA214AB1F47908EC13.zip

Specification Pattern

이 패턴에 대해서, 위키피디아는 “Recombinable business logic in a boolean fashion” 이라고 정의하고 있다. 이 말은 비지니스 로직을 처리하는데 if~else를 많이 쓰는데, 이 if~else를 제거하고 비지니스 로직을 확장하기 쉽게 Composition하는 패턴이라고 설명을 하는 것이다. 그리고, 로직이 다른 개별 클래스가 계속 추가 된다면, Strategy Pattern과 유사한 형태가 되겠다. 하지만, Strategy Pattern은 데이타에 대한 처리나 체크를 Concrete Strategy에서 하고 Specification Pattern은 모델의 Provider 클래스가 처리를 대행하는 형태라는게 다르다.

아래 링크도 참고하면 좋겠다.
http://en.wikipedia.org/wiki/Specification_pattern#C.23

아래는 예제 코드이다.

package net.sjava.patterns.specification;

/**
 * 
 * @author mcsong@gmail.com
 * @since 2009. 9. 16.
 */
public class User {
	/**
	 * name
	 */
	private String name;
	/**
	 * city
	 */
	private String city;
	/**
	 * address
	 */
	private String address;
	/**
	 * company
	 */
	private String companyName;

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the city
	 */
	public String getCity() {
		return city;
	}

	/**
	 * @param city
	 *            the city to set
	 */
	public void setCity(String city) {
		this.city = city;
	}

	/**
	 * @return the address
	 */
	public String getAddress() {
		return address;
	}

	/**
	 * @param address
	 *            the address to set
	 */
	public void setAddress(String address) {
		this.address = address;
	}

	/**
	 * @return the company
	 */
	public String getCompanyName() {
		return companyName;
	}

	/**
	 * @param company
	 *            the company to set
	 */
	public void setCompanyName(String company) {
		this.companyName = company;
	}
}
package net.sjava.patterns.specification;

/**
 * 
 * @author mcsong@gmail.com
 * @since 2009. 9. 16.
 */
public interface IUserSpecification {
	/**
	 * 
	 * @param user
	 * @return
	 */
	public boolean isSatisfied(User user);
}
package net.sjava.patterns.specification;

/**
 * 
 * @author mcsong@gmail.com
 * @since 2009. 9. 16.
 */
public abstract class AbstractUserSpecification implements IUserSpecification {
	@Override
	public boolean isSatisfied(User member) {
		// TODO Auto-generated method stub
		return true;
	}
}
package net.sjava.patterns.specification;
 
/**
 * 
 * @author mcsong@gmail.com
 * @since 2009. 9. 16.
 */
public class CompanySpecification extends AbstractUserSpecification {
    /**
	*
	*/
    private String companyName;
 
    /**
     * 
     * @param companyName
     */
    public CompanySpecification(String companyName) {
        this.companyName = companyName;
    }
 
    @Override
    public boolean isSatisfied(User user) {
        // TODO Auto-generated method stub
        if (user == null)
            return false;
 
        return user.getCompanyName().equals(this.companyName);
    }
}
package net.sjava.patterns.specification;

import java.util.ArrayList;

/**
 * 
 * @author mcsong@gmail.com
 * @since 2009. 9. 16.
 */
public class UserProvider {
	/**
	*
	*/
	private static ArrayList<User> users = new ArrayList<User>();
	/**
	*
	*/
	private static java.util.concurrent.atomic.AtomicBoolean isLoaded = null;

	/**
	*
	*/
	private UserProvider() {
		if (isLoaded == null) {
			isLoaded = new java.util.concurrent.atomic.AtomicBoolean();
			UserProvider.isLoaded.set(true);
			UserProvider.provideUsers();

		}
	}

	/**
	 * 
	 * @return
	 */
	public static UserProvider provider() {
		return new UserProvider();
	}

	/**
	 * dummy data 입력
	 */
	public static void provideUsers() {
		for(int i=0; i < 10; i++) {
			User user = new User();
			user.setName(String.valueOf(i));
			user.setCity("서울");
			user.setAddress(String.valueOf(i) + " 번지");
			if(i < 5)
			user.setCompanyName("회사");
			else
			user.setCompanyName("백수");
			 
			users.add(user);
		}
	}

	/**
	 * 
	 * @param user
	 * @return
	 */
	public ArrayList<User> getSpecificationList(IUserSpecification specification) {
		ArrayList<User> list = new ArrayList<User>();
		for(int i=0; i < users.size(); i++) {
			if(specification.isSatisfied(users.get(i)))
				list.add(users.get(i));
		}
		return list;
	}
}
import java.util.ArrayList;
import net.sjava.patterns.specification.User;
import net.sjava.patterns.specification.UserProvider;
import net.sjava.patterns.specification.IUserSpecification;
import net.sjava.patterns.specification.CompanySpecification;

public class SpecificationPatternTest {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ArrayList<User> users = null;
		IUserSpecification spec = null;
		spec = new CompanySpecification("회사");
		users = UserProvider.provider().getSpecificationList(spec);

		for (int i = 0; i < users.size(); i++) {
			System.out.println(i + users.get(i).getCompanyName());
		}

		spec = new CompanySpecification("백수");
		users = UserProvider.provider().getSpecificationList(spec);

		for (int i = 0; i < users.size(); i++)
			System.out.println(i + users.get(i).getCompanyName());
		
	}
}

cfile26.uf.183E69224AB1F4DC9FC5DB.zip

디자인 패턴 그리고 그 이후..

몇일전에 에릭감마의 내한 세미나에 참석을 하였습니다.
거기서, 에릭감마는 디자인 패턴이 지금은 구식이 되어 버린 경우가 생겼고, 그것에 대한 계속적인 스터디가 필요하다고 말했던 것으로 기억을 하고 있습니다(아님 말고.. ^^;;).

위의 상황에 대한 적절한 스터디를 할 수 있는 곳이 있더군요.. ^^
아 좋네요.. concurreny에 대한 내용도 있고..

http://en.wikipedia.org/wiki/Software_design_pattern