Caleb의 대답은 그가 올바른 길을 가고 있지만 실제로 잘못되었습니다. 그의 Foo수업은 데이터베이스 외관과 팩토리 역할을합니다. 이것들은 두 가지 책임이며 단일 수업에 참여해서는 안됩니다.
특히 데이터베이스 컨텍스트에서이 질문이 너무 많이 요청되었습니다. 여기에서는 추상화 (인터페이스 사용)를 사용하여 응용 프로그램의 결합을 줄이고 활용 성을 높이는 이점을 철저히 보여줄 것입니다.
더 읽기 전에, Dependency injection 을 아직 모른다면 읽고 이해하는 것이 좋습니다 . 또한 기본적으로 인터페이스의 공용 메소드 뒤에 구현 세부 사항을 숨기는 것이 의미 하는 어댑터 디자인 패턴 을 점검 할 수도 있습니다 .
팩토리 디자인 패턴 과 결합 된 의존성 주입 은 IoC 원칙 의 일부인 전략 디자인 패턴 을 코딩하는 쉬운 방법 입니다.
전화하지 마십시오 . 전화하겠습니다 . (일명 할리우드 원칙 ).
추상화를 사용하여 응용 프로그램 분리
1. 추상화 레이어 만들기
C ++와 같은 언어로 코딩하는 경우 인터페이스 또는 추상 클래스를 작성하고이 인터페이스에 일반 메소드를 추가하십시오. 인터페이스와 추상 클래스는 모두 직접 사용할 수없는 동작을 가지고 있지만 인터페이스의 경우 (인터페이스의 경우) 구현하거나 추상적 인 클래스의 경우 확장해야하므로 코드 자체에서 이미 제안합니다. 인터페이스 또는 추상 클래스가 제공하는 계약을 완전히 채우려면 특정 구현이 필요합니다.
귀하의 (매우 간단한 예) 데이터베이스 인터페이스는 다음과 같습니다 (DatabaseResult 또는 DbQuery 클래스는 각각 데이터베이스 작업을 나타내는 자체 구현이 됨).
public interface Database
{
DatabaseResult DoQuery(DbQuery query);
void BeginTransaction();
void RollbackTransaction();
void CommitTransaction();
bool IsInTransaction();
}
이것은 인터페이스이기 때문에 실제로는 아무것도하지 않습니다. 따라서이 인터페이스를 구현하려면 클래스가 필요합니다.
public class MyMySQLDatabase : Database
{
private readonly CSharpMySQLDriver _mySQLDriver;
public MyMySQLDatabase(CSharpMySQLDriver mySQLDriver)
{
_mySQLDriver = mySQLDriver;
}
public DatabaseResult DoQuery(DbQuery query)
{
// This is a place where you will use _mySQLDriver to handle the DbQuery
}
public void BeginTransaction()
{
// This is a place where you will use _mySQLDriver to begin transaction
}
public void RollbackTransaction()
{
// This is a place where you will use _mySQLDriver to rollback transaction
}
public void CommitTransaction()
{
// This is a place where you will use _mySQLDriver to commit transaction
}
public bool IsInTransaction()
{
// This is a place where you will use _mySQLDriver to check, whether you are in a transaction
}
}
이제 Database인터페이스 를 구현하는 클래스 가 생겼습니다. 인터페이스가 유용 해졌습니다.
2. 추상화 계층 사용
응용 프로그램 어딘가에 메서드 SecretMethod가 있습니다. 재미를 위해 메서드를 호출합시다. 이 메서드 내에서 일부 데이터를 가져 오기 위해 데이터베이스를 사용해야합니다.
이제 인터페이스를 만들 수 있습니다. 직접 만들 수는 없지만 (어떻게 사용하는지) 키워드를 MyMySQLDatabase사용하여 구성 할 수 있는 class 가 있습니다 new.
큰! 데이터베이스를 사용하고 싶습니다 MyMySQLDatabase.
방법은 다음과 같습니다.
public void SecretMethod()
{
var database = new MyMySQLDatabase(new CSharpMySQLDriver());
// you will use the database here, which has the DoQuery,
// BeginTransaction, RollbackTransaction and CommitTransaction methods
}
이것은 좋지 않습니다. 이 메소드 내에서 직접 클래스를 작성하고 있으며 내부에서 클래스를 수행하는 경우 SecretMethod30 개의 다른 메소드에서 동일한 작업을 수행한다고 가정하는 것이 안전합니다. 당신이 변경을 원하는 경우 MyMySQLDatabase등, 다른 클래스에 MyPostgreSQLDatabase, 당신은 모든 30의 방법에서 변경해야합니다.
또 다른 문제점은 작성 MyMySQLDatabase실패가 발생하면 메소드가 완료되지 않으므로 유효하지 않다는 것입니다.
우리 MyMySQLDatabase는 매개 변수로 메소드에 매개 변수를 전달 하여 생성을 리팩토링하는 것으로 시작합니다 (이를 종속성 주입이라고 함).
public void SecretMethod(MyMySQLDatabase database)
{
// use the database here
}
이렇게하면 MyMySQLDatabase개체를 만들 수없는 문제가 해결 됩니다. SecretMethod유효한 MyMySQLDatabase객체를 기대 하기 때문에 어떤 일이 발생하고 객체가 객체에 전달되지 않으면 메서드가 실행되지 않습니다. 그리고 그것은 완전히 괜찮습니다.
일부 응용 프로그램에서는 이것으로 충분할 수 있습니다. 만족할 수도 있지만 더 나은 리팩토링을하자.
다른 리팩토링의 목적
보시다시피, 지금 객체를 SecretMethod사용하고 MyMySQLDatabase있습니다. MySQL에서 MSSQL로 이동했다고 가정 해 봅시다. 당신은 정말 당신의 내부에 모든 로직을 변경 기분이 안 SecretMethod하는 호출하는 방법 BeginTransaction과 CommitTransaction상의 방법을 database새 클래스를 만들 수 있도록 매개 변수로 전달 변수 MyMSSQLDatabase도있을 것이다, BeginTransaction그리고 CommitTransaction방법을.
그런 다음 선언을 SecretMethod다음으로 변경하십시오 .
public void SecretMethod(MyMSSQLDatabase database)
{
// use the database here
}
그리고 클래스 MyMSSQLDatabase와 MyMySQLDatabase동일한 메소드가 있기 때문에 다른 것을 변경할 필요가 없으며 여전히 작동합니다.
아 잠깐만!
구현 하는 Database인터페이스 MyMySQLDatabase가 있습니다. MyMSSQLDatabase클래스 도 있습니다. 클래스 는 클래스와 동일합니다 MyMySQLDatabase. 아마도 MSSQL 드라이버가 Database인터페이스를 구현할 수도 있으므로 정의에 추가합니다.
public class MyMSSQLDatabase : Database { }
그러나 MyMSSQLDatabasePostgreSQL로 전환했기 때문에 앞으로 더 이상 사용하지 않으려면 어떻게해야 합니까? 다시 정의를 바꿔야 SecretMethod합니까?
예, 그렇습니다. 그리고 그것은 제대로 들리지 않습니다. 지금 우리는 것을 알고 MyMSSQLDatabase와 MyMySQLDatabase같은 방법이 있고 구현 모두 Database인터페이스를. 그래서 당신 SecretMethod은 이것을 다음과 같이 리팩토링합니다 .
public void SecretMethod(Database database)
{
// use the database here
}
SecretMethodMySQL, MSSQL 또는 PotgreSQL을 사용하는지 여부를 더 이상 알지 못하는 방법에 주목 하십시오. 데이터베이스를 사용한다는 것을 알고 있지만 특정 구현에 대해서는 신경 쓰지 않습니다.
예를 들어 PostgreSQL과 같은 새 데이터베이스 드라이버를 만들려면 전혀 변경할 필요가 없습니다 SecretMethod. 을 만들고 MyPostgreSQLDatabase, Database인터페이스를 구현 하게하고 , PostgreSQL 드라이버 코딩을 마치고 작동하면 인스턴스를 생성하여에 삽입합니다 SecretMethod.
3. 원하는 구현을 얻기 Database
를 호출하기 전에 원하는 인터페이스 SecretMethod구현 Database(MySQL, MSSQL 또는 PostgreSQL인지 여부)을 결정해야합니다. 이를 위해 팩토리 디자인 패턴을 사용할 수 있습니다.
public class DatabaseFactory
{
private Config _config;
public DatabaseFactory(Config config)
{
_config = config;
}
public Database getDatabase()
{
var databaseType = _config.GetDatabaseType();
Database database = null;
switch (databaseType)
{
case DatabaseEnum.MySQL:
database = new MyMySQLDatabase(new CSharpMySQLDriver());
break;
case DatabaseEnum.MSSQL:
database = new MyMSSQLDatabase(new CSharpMSSQLDriver());
break;
case DatabaseEnum.PostgreSQL:
database = new MyPostgreSQLDatabase(new CSharpPostgreSQLDriver());
break;
default:
throw new DatabaseDriverNotImplementedException();
break;
}
return database;
}
}
보시다시피 팩토리는 구성 파일에서 사용할 데이터베이스 유형을 알고 있습니다 (다시 말해 Config클래스는 자체 구현 일 수 있음).
이상적으로는 DatabaseFactory의존성 주입 컨테이너 안에 있습니다. 그러면 프로세스가 다음과 같이 보일 수 있습니다.
public class ProcessWhichCallsTheSecretMethod
{
private DIContainer _di;
private ClassWithSecretMethod _secret;
public ProcessWhichCallsTheSecretMethod(DIContainer di, ClassWithSecretMethod secret)
{
_di = di;
_secret = secret;
}
public void TheProcessMethod()
{
Database database = _di.Factories.DatabaseFactory.getDatabase();
_secret.SecretMethod(database);
}
}
프로세스에서 특정 데이터베이스 유형을 작성하는 방법은 없습니다. 뿐만 아니라, 당신은 전혀 아무것도 만들지 않습니다. 구성에 따라 올바른 인터페이스 인스턴스를 리턴하는 메소드 인 종속성 주입 컨테이너 ( 변수) 내에 저장된 오브젝트 에서 GetDatabase메소드를 호출 합니다.DatabaseFactory_diDatabase
PostgreSQL을 사용한지 3 주 후에 MySQL로 돌아가려면 단일 구성 파일을 열고 DatabaseDriverfield 값을 에서 DatabaseEnum.PostgreSQL로 변경하십시오 DatabaseEnum.MySQL. 그리고 당신은 끝났습니다. 갑자기 나머지 응용 프로그램은 한 줄을 변경하여 MySQL을 다시 올바르게 사용합니다.
여전히 놀랍지 않다면 IoC에 대해 좀 더 자세히 살펴 보는 것이 좋습니다. 구성이 아니라 사용자 입력에서 특정 결정을 내리는 방법. 이러한 접근 방식을 전략 패턴이라고하며 엔터프라이즈 응용 프로그램에서 사용할 수 있고 사용하지만 컴퓨터 게임을 개발할 때 훨씬 더 자주 사용됩니다.
DbQuery예를 들어 물건을 가져 가십시오 . 해당 개체에 SQL 쿼리 문자열을 실행할 멤버가 있다고 가정하면 어떻게 일반화 할 수 있습니까?