[Design pattern - 실무편] FactoryDao 예제 ※ DI(의존성 주입) 구현 예제


Study/Design Pattern  2019. 6. 12. 23:09

안녕하세요. 명월입니다.

 

지금까지 기본적인 디자인 패턴을 전부 소개했습니다. 이번 글부터는 디자인 패턴이 실무에서 어떻게 응용이 되는 지 소개하겠습니다.

제가 디자인 패턴 중에 가장 많이 사용하는 방법 중 하나가 DAO(Database access object) 관리를 팩토리 패턴과 경량 패턴(Flyweight) 그리고 싱글 톤으로 묶어서 Factory Dao로 만들어서 사용합니다.

//Dao의 인터페이스
interface IDao
{

}
// 템플릿 메서드 패턴식
abstract class AbstractDao<T> : IDao
{
  // Id에 의해 하나의 Entity를 검색
  public T SelectByOne(int id)
  {
    return default(T);
  }
  // Insert
  public int Insert(T entity)
  {
    return 0;
  }
  // Update
  public int Update(T entity)
  {
    return 0;
  }
  // Delete
  public int Delete(T entity)
  {
    return 0;
  }
}
// Entity
class AEntity
{

}
class BEntity
{

}
// Entity Dao
class AEntityDao : AbstractDao<AEntity>
{
  public IList<AEntity> SelectAll()
  {
    return null;
  }
}
class BEntityDao : AbstractDao<BEntity>
{
  public IList<BEntity> SelectAll()
  {
    return null;
  }
}

위 소스는 예제이기 때문에 Entity와 Dao의 내용을 특별히 만들지는 않았습니다.

 

만약 소스가 위의 조건처럼 구성이 되어 있으면 저는 Factory method pattern으로 Dao를 관리합니다. Factory method pattern + flyweight로 하면 dao의 리소스를 한곳에서 관리가 가능하기 때문입니다.

class FactoryDao
{
  // FactoryDao를 여러번 만들 필요가 없기 때문에 Singleton으로 만든다.
  private static FactoryDao instance = null;
  public static FactoryDao GetInstance()
  {
    if (instance == null)
    {
      instance = new FactoryDao();
    }
    return instance;
  }

  // Dao등도 데이터를 가지고 있는 클래스가 아닌 Database를 접속하기 위한 클래스이기 때문에 Singleton -> flyweight 패턴으로 구성한다.
  private Dictionary<Type, IDao> flyweight = new Dictionary<Type, IDao>();
  // AEntityDao를 얻는 함수
  public AEntityDao GetAEntityDao()
  {
    if (!flyweight.ContainsKey(typeof(AEntityDao)))
    {
      flyweight.Add(typeof(AEntityDao), new AEntityDao());
    }
    return (AEntityDao)flyweight[typeof(AEntityDao)];
  }
  // BEntityDao를 얻는 함수
  public BEntityDao GetBEntityDao()
  {
    if (!flyweight.ContainsKey(typeof(BEntityDao)))
    {
      flyweight.Add(typeof(BEntityDao), new BEntityDao());
    }
    return (BEntityDao)flyweight[typeof(BEntityDao)];
  }
}

class Program
{
  static void Main(string[] args)
  {
    IList<AEntity> test = FactoryDao.GetInstance().GetAEntityDao().SelectAll();

    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

실무에서는 위처럼 FactoryDao를 구성하는 경우가 많습니다. 이런 형식은 이제는 거의 표준처럼 FactoryDao를 작성합니다.

여기서 저는 Reflection기능을 써서 FactoryDao를 더 패턴화 시킬 수 있습니다.

class FactoryDao
{
  private static FactoryDao instance = null;
  private Dictionary<Type, IDao> flyweight = new Dictionary<Type, IDao>();
  public static object GetDao(Type type)
  {
    //FacotryDao의 싱글톤 패턴이다.
    if (instance == null)
    {
      instance = new FactoryDao();
    }
    // Replection 기능을 이용해서 Instance를 생성한다.
    if (!instance.flyweight.ContainsKey(type))
    {
      instance.flyweight.Add(type, (IDao)Activator.CreateInstance(type));
    }
    return instance.flyweight[type];
  }
  public static T GetDao<T>() where T : IDao
  {
    return (T)GetDao(typeof(T));
  }  
}

class Program
{
  static void Main(string[] args)
  {
    IList<AEntity> test = FactoryDao.GetDao<AEntityDao>().SelectAll();

    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

Refleaction을 사용하기 전에는 Dao를 만들 때마다 FactoryDao에도 함수를 추가해야 합니다. 그러나 Reflection을 이용하면 단지 IDao만 상속받은 클래스라면 FactoryDao를 수정하지 않아도 Factory pattern을 이용할 수 있는 이점이 있습니다.

실제 실무에서는 Dao를 사용할 때마다 인스턴스를 만들어서 사용하지 않습니다. 왜냐하면 connection의 문제도 있고 lock를 제어하기도 힘들기 때문입니다.

 

그래서 FactoryDao를 사용하고 최근에는 DI(의존성 주입)의 개념이 생겨서 내부적으로 Dao를 선언하여 사용하는 경우가 많습니다.

//의존성 주입을 위한 어트리뷰트
[AttributeUsage(AttributeTargets.Field)]
public class Database : Attribute
{
}
abstract class AbstractController
{
  public AbstractController()
  {
    // Database 어트리뷰트를 가진 변수를 찾아라!
    var daofield = this.GetType()
               .GetFields(System.Reflection.BindingFlags.Public | 
                               System.Reflection.BindingFlags.NonPublic | 
                               System.Reflection.BindingFlags.Instance)
               .Where(x => x.GetCustomAttributes(true).Where(a => typeof(Database).Equals(a.GetType())).Any())
               .ToList();
    daofield.ForEach(x =>
    {
      // FactoryDao를 통해 Database 어트리뷰트에 의존성 주입을 시작한다.
      x.SetValue(this, FactoryDao.GetDao(x.FieldType));
    });
  }
}
class Controller : AbstractController
{
  // AEntityDao를 의존성 주입으로 넣겠다.
  [Database]
  private AEntityDao aEntityDao;

  public Controller() : base()
  {
    // 변수가 Null이 아니다.
    Console.WriteLine(aEntityDao.GetHashCode());
  }
}

예로 FactoryDao를 이용한 의존성 주입 예제를 만들어 봤습니다. 실제로 Spring이나 MVC에서의 의존성 주입은 위처럼 구현될 꺼라고 생각합니다.

소스를 보면 AEntityDao의 변수에 새로 new를 통한 인스턴스 생성을 하지 않았는데도 불구하고 FactoryDao를 통해 인스턴스가 생성되어 있습니다.

 

여기까지 FactoryDao에 대한 설명이었습니다.

 

궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.