소프트웨어 이야기/착한 프로그래밍

간단한 C++ 전략 패턴

이현봉 2012. 12. 11. 19:40

#include <iostream> #include <typeinfo>   // typeid 를 사용하기 위해서 using namespace std; // 문제 : 디바이스 타입별로 읽기, 쓰기가 다르고 앞으로도 계속 새로운 디바이스 타입들이 나올텐데 어떻게 하면 기존 프로그램의 수정을 최소로 하고 기계적으로  //        새로운 디바이스를 지원할 수 있을까? // 해결책 : 디바이스 타입별로 다른 읽기/쓰기를 독립된 각각 독립된 객체로 만들고 이 것을 이용하는 client에 이 객체들을 담을 수 있는 추상클래스를 갖도록 하고 //         전체 디바이스를 아우르는 Device 추상 클래스를 만들고 구체적인 디바이스 타입별 클래스는 이 녀석을 상속한다. Client는 추상클래스(인터페이스)에 대해 프로그래밍한다.  //         Client는 추상클래스에 대해 읽기/쓰기를 하기 전에 추상클래스 변수에 실제 디바이스별 읽기/쓰기 //         객체가 들어가 있도록 해야한다.  즉, 전략패턴을 사용한다.  여기서 전략은 디바이스 타입별로 적용하는 "읽기"와 "쓰기"이다.  //         전략의 유연성이 Device 추상클래스내 읽기, 쓰기 추상클래스가 주는 polymorphism을 축으로 갖추어졌다.    // ----------------------- CReadAbstract 추상 클래스 : C++ 에서는 java와 달리 interface가 없어 abstract class로 만듬 ---------------------------------- class CReadAbstract    // 디바이스를 읽는 것을 전략 패턴을 써 만든다. 나중에 이 변수 타입에 디바이스 타입별 실제 "디바이스 읽기 객체(전략)"가 대응된다. { public: virtual void read() = 0; // CReadAbstract 는 Abstract class because of this pure virtual function virtual ~CReadAbstract() { cout << "In CReadAbstract destructor!" << endl; } // 기타 메소드들 private: // 기타 attribute과 메소드들 }; class CRead_Android_SSGalaxy3 : public CReadAbstract   // 실제로 Android_SSGalaxy3 를 읽는 객체.   { public: void read()      // CReadAbstract에서 파생된 실제 객체이기에 CReadAbstract에 규정된 read()를 Android_SSGalaxy3에 맞게 구현해야 함. { cout << "I am reading Android_SSGalaxy3" << endl;   // 간단한 예 } // 기타 CRead_Android_SSGalaxy3 에 필요한 attribute과 메소드들 }; class CRead_iPhone4GS : public CReadAbstract   // 실제로 iPhone4GS 를 읽는 객체. { public: void read() { cout << "I am reading iPhone4GS " << endl; } // 기타 attribute과 메소드들 }; // ----------------------- CReadAbstract 추상 클래스와 파생 concrete 클래스 종료  ------------------------- // ----------------------- CWriteAbstract 추상 클래스 :  ------------------------- class CWriteAbstract { public: virtual void write() = 0; // make this class to Abstract class by this pure virtual function virtual ~CWriteAbstract() { cout << "In CWriteAbstract destructor!" << endl; } // 기타 attribute과 메소드들 }; class CWrite_Android_SSGalaxy3 : public CWriteAbstract { public: void write() { cout << "I am writing Android_SSGalaxy3" << endl; } // 기타 attribute과 메소드들 }; class CWrite_iPhone4GS : public CWriteAbstract { public: void write() { cout << "I am writing iPhone4GS" << endl; } // 기타 attribute과 메소드들 }; // ----------------------- End of CWriteAbstract 추상 클래스 & child classes  ------------------------- // ----------------------- Device class.  Device 클래스는 추상 클래스 ------------------------- class CDevice { public: enum DeviceType {Android_SSGalaxy3, iPhone4GS, LG_F860};  // 나중에 활용... CReadAbstract  *readType ;       // CDevice 는 내부에 CReadAbstract 타입의 멤버 변수를 갖고 있음 CWriteAbstract *writeType ; virtual void getDeviceType() = 0; // Make CDevice an abstract class by having a pure virtual function void readDevice() { readType->read();    // CDevice 에서 readDevice()를 호출하면 실제로 이것으로 위임(Delegate).  readType에 이미 객체가 대응되어 있어야 함.   } void setReadDevice(CReadAbstract *device_Read) { cout << "읽어야 할 디바이스를 변경..." << endl; delete readType; // Delete existing readTemplate before assigning new device instance readType = device_Read; } void writeDevice() { writeType->write(); } void setWriteDevice(CWriteAbstract *device_Write) { cout << "Write 할 디바이스를 변경......" << endl; delete writeType; writeType = device_Write; } //  Provide a destructor to free the memory used by this behaviours virtual ~CDevice() { cout << endl << "In Device destructor deleting read and write template behaviours..." << endl; delete readType; delete writeType; } private: DeviceType thisDeviceType; // put attributes & methods that seem to fit. }; // ------------------------  make this a concrete class derived from CDevice interface, I mean abstract class ------------------------------ class CAndroid_SSGalaxy3 : public CDevice   // CDevice is an abstract class { public: CAndroid_SSGalaxy3() { readType = new CRead_Android_SSGalaxy3();   // Android_SSGalaxy3 를 읽기 위해 필요한 구체적인 CRead_Android_SSGalaxy3 객체(전략)을 대응시킴 writeType = new CWrite_Android_SSGalaxy3();  // Android_SSGalaxy3에 쓰기 위해 필요한 구체적인 CWrite_Android_SSGalaxy3 객체(전략)을 대응시킴 } void getDeviceType() // got to implement this { cout << "나의 정체는 Android_SSGalaxy3" << endl; } void testfn() { test(); } // 기타 attribute과 메소드들 private: void test() { cout << "RTTI/Reflection test in Android_SSGalaxy3. Use real reflection library, man. " << endl ; } }; class CiPhone4GS : public CDevice { public: CiPhone4GS() { readType = new CRead_iPhone4GS(); writeType   = new CWrite_iPhone4GS(); } void getDeviceType() { cout << "I am iPhone4GS, really" << endl; } // 기타 attribute과 메소드들 }; // ----------------------- End of Device subtypes ------------------------- //  만약에 이 상태에서 새로운 디바이스 LG-F180 (옵티머스G) 를 읽고/쓰기해야 하면 // 1.  LG-F180(Android_LG_OptimusG)를 구체적으로 읽는 객체/전략을 CReadAbstract 클래스의 파생 클래스로 만든다.   //     CReadAbstract는 추상클래스이기에 파생 클래스에서 인스턴스 생성하려면 read()를 구현해야 한다. class CRead_Android_LG_OptimusG : public CReadAbstract { public: void read() { cout << "I am reading Android_LG_OptimusG" << endl; } // 기타 attribute과 메소드들 }; // 2.  LG-F180에 구체적으로 쓰는 객체/전략을 CWriteAbstract 클래스의 파생 클래스로 만든다. class CWrite_Android_LG_OptimusG : public CWriteAbstract { public: void write()   // 이것을 구현해야 함.   { cout << "I am writing Android_LG_OptimusG" << endl; } // 기타 attribute과 메소드들 }; // 만약 LG-F180에 구체적으로 쓰는 객체/전략이 Android_SSGalaxy3 와 똑 같다면 그냥 CWrite_Android_SSGalaxy3 을 상속하면 됨. //class CWrite_Android_LG_OptimusG : public CWrite_Android_SSGalaxy3 {}; // 3. CDevice 추상 클래스의 파생 클래스 형태로 Android_LG_OptimusG 클래스를 만든다.  Android_LG_OptimusG instance  // ------------------------  구체적인 디바이스 타입들을 위의 Device 추상 클래스로부터 파생 ------------------------------ class Android_LG_OptimusG : public CDevice   // CDevice는 abstract class { public: Android_LG_OptimusG() { readType = new CRead_Android_LG_OptimusG();   // initialize strategy that Android_SSGalaxy3 will use writeType   = new CWrite_Android_LG_OptimusG(); } void getDeviceType() { cout << "나의 정체는 Android_LG_OptimusG" << endl;   } // 기타 attribute과 메소드들 }; /* template<class T> class DeviceFactory { public: DeviceFactory(T deviceType) { } }; */ //  simple no brain factory...   새로운 디바이스 타입이 생기면 여기에 추가해야 함. class CStaticDeviceFactory    // 디바이스 인스턴스를 생성해 반환해 주는 넘..... { public: static CDevice * createDevice(string strDeviceType)    { if (strDeviceType.compare("Android_SSGalaxy3") == 0) return new CAndroid_SSGalaxy3(); else if (strDeviceType.compare("Android_LG_OptimusG") == 0 ) return new Android_LG_OptimusG(); else if (strDeviceType.compare("iPhone4GS") == 0 ) return new CiPhone4GS(); } private: CDevice *deviceInstance; // 기타 attribute과 메소드들 }; // ----------------------- The main event ------------------------- int main() { // -----------  1번 : 먼저 읽을 핸드폰 종류가 무엇인지 파악한다. // string deviceType_Read = CDevRecognizer.identifyDevice();   // CDevRecognizer : 디바이스가 어떤 타입인가를 인식해주는 클래스. 향후 구현해야 함   string deviceType_Read = "Android_SSGalaxy3";   // 테스트를 위해서 임시... // ----------- 2번 : 핸드폰 종류를 알았으니 그 핸드폰 객체를 생성한다.  CDevice *deviceTo_Read = CStaticDeviceFactory::createDevice(deviceType_Read);   // 객체생성공장으로 부터 맞는 객체를 생성해 가져온다. // -----------  3번 : 핸드폰을 읽는다. if (deviceTo_Read == NULL) cout << "Wake Up!  Put the right object before you call me. " << endl; else deviceTo_Read->readDevice();    // 이것은 바뀌지 않음.   // dynamic_cast 를 이용해 실행 중에 다형성 변수에 있는 객체 타입을 subtype 객체로 바꾸는 연습.   cout << endl << "dynamic_cast 활용 연습" << endl; CAndroid_SSGalaxy3* android_SSGalaxy3 = dynamic_cast<CAndroid_SSGalaxy3*>(deviceTo_Read) ;   // dynamic_cast 실행. 만약 실패하면 NULL을 반환 if (android_SSGalaxy3 !=  NULL)   // // base to derived class 로의 dynamic_cast가 성공했는지 확인.   android_SSGalaxy3->testfn(); //dynamic_cast<Android_SSGalaxy3*>(device_Read)->testfn();   // base to derived class 로의 dynamic_cast가 성공하려면 base class가 polymorphic (abstract) 클래스이어야 함.  dynamic_cast는 비용이 큼 // typeid 를 이용해 실행 중에 다형성 변수에 어떤 객체가 대응되어 있는 지 파악하는 연습 cout << endl << "typeid 활용 연습" << endl; cout << "deviceType_Read compile time type is  : " << typeid(deviceTo_Read).name() << endl; cout << "deviceType_Read runtime dynamic type is : " << typeid(&*deviceTo_Read).name() << endl; cout << endl ; // -----------  4번 : 쓰기 핸드폰을 인식한다. // string deviceType_Write = CDevRecognizer.identifyDevice();   // 디바이스가 어떤 타입인가를 인식했다는 것을 가정하고. string deviceType_Write = "iPhone4GS"; // -----------  5번 : 쓰기 핸드폰을 생성한다 CDevice *deviceTo_Write = CStaticDeviceFactory::createDevice(deviceType_Write); //CDevice *deviceTo_Write = new iPhone4GS();   // 쓰기할 디바이스 // -----------  6번 : 쓰기 핸드폰에 쓴다 deviceTo_Write->writeDevice(); cout << endl ; // runtime에 iPhone_4GS 의 쓰기 방식만을 변경함.   cout << "iPhone_4GS의 write 방식만을 android_SSGalaxy3 방식으로 실행중에 변경 " << endl; deviceTo_Write->setWriteDevice(new CWrite_Android_SSGalaxy3() ); deviceTo_Write->readDevice(); deviceTo_Write->writeDevice(); // 읽어야 할 폰의 타입을 Android_LG_OptimusG 으로 변경 cout << endl << "인식을 잘 못 해서 읽는 폰을 Android_LG_OptimusG으로 변경 " << endl; deviceTo_Read->setReadDevice(new CRead_Android_LG_OptimusG) ; deviceTo_Read->readDevice();// cout << endl << "쓰기할 폰을 Android_LG_OptimusG으로 함 " << endl; deviceTo_Write = CStaticDeviceFactory::createDevice("Android_LG_OptimusG"); deviceTo_Write->writeDevice(); // 생성한 객체를 삭제.  메모리 누수 방지. delete deviceTo_Read; delete deviceTo_Write; int i; cin >> i; return 0; }


'소프트웨어 이야기 > 착한 프로그래밍' 카테고리의 다른 글

착한 프로그래밍  (0) 2012.06.01