26. 의존 제거하기 0 단계 인터페이스 추출 (Extract Interface) 구현 추출 (Extract Implementor)
27.
28.
29.
30.
31.
32.
33.
34. 의존 제거하기 1 단계 함수에 매개변수 추가 (Parameterize Method) 함수 추출 후 오버라이딩 (Extract and Override Call)
35.
36. 예 : 몬스터가 공격 당할 때 void CMonster ::Attack(CActor* pTarger) { double angerBonus = 1.0; if (m_VeryAngry) { CAngryData* pAngry = new CAngryData(this); angerBonus = pAngry->GetBonus(pTarget); } void CMonster ::Attack(CActor* pTarger) { CAngryData* pAngryData = NULL; if (m_VeryAngry) { pAngryData = new CAngryData(); } Attack(pTarget, pAngryData); void CMonster ::Attack(CActor* pTarger, CAngryData* pAngryData) { double angerBonus = 1.0; if (pAngryData) { angerBonus = pAngry->GetBonus(pTarget);
37.
38.
39.
40. 함수 추출 후 오버라이딩 - 예 void CPlayer::Attack(CActor* pTarger) { ... WSASend(m_Handle...); // 공격과 함께 데미지 표시 // 변경 후 void CPlayer::Attack(CActor* pTarger) { ... m_Socket.Send(...); // 공격과 함께 데미지 표시 class CSocket { void Send(...) { WSASend(m_Handle, ...); class CMockSocket : public CSocket { void Send(...) { // do nothing
41. 더 할 수 있다 class CMockSocket : public CSocket { void Send(int packetType) { m_PacketRecorder.Push(packetType); TEST_FIXTURE(FixtureBattle, PlayerSendBeAttackedPacket) { pMonster->Attack(pPlayer); CHECK(pPlayer->IsSentPacket(PacketType::BeAttacked)); 이런 걸 감지 변수 (sensing) 이라고 한다 .
42.
43.
44. Seam 예제 void CPlayer::Logout() { g_Database.SavePlayerStatus(this); ... } CDatabase::SavePlayerStatus() 를 하기 위해서는 꼭 DB 에 접근해야 하고 , 시간이 5 초 이상 걸린다고 하면 ?
45. 가상함수 이용 - 예 CPlayer::CPlayer(CDatabase* pDatabase) : m_pDatabase(pDatabase) {} void CPlayer::Logout() { m_pDatabase->SavePlayerStatus(this); // CDatabase* CPlayer::m_pDatabase; } // 아까의 CMockSocket 과 비슷한 맥락임 . class CMockDatabase : public CDatabase { bool SavePlayerStatus(CPlayer* pPlayer) { return true; } class CMockPlayer : public CPlayer... CMockPlayer* pPlayer1 = new CMockPlayer(new CMockDatabase());
46.
47. define 을 이용한 코드 변경 - 예 void CPlayer::Logout() { CDatabase::Inst().SavePlayerStatus(this); // singleton 사용 ... } class CDatabase { static CDatabase& Inst() { #ifdef __UnitTest__ if (m_bTesting) { static CMockDatabase s; return s; } #endif static CDatabase s; return s; }
51. 의존 제거하기 3 단계 정적 함수 노출하기 (Expose Static Method)
52. 예 : CPlayer 의 공격 코드 class CPlayer : public CActor { bool CanHit(CActor*); void Attack(CActor* pTarget); ... void CPlayer::Attack(CActor* pTarget) { if (CanHit(pTarget)) { pTarget->BeAttacked(this); } }
53.
54. CPlayer 의 공격 코드 - 수정판 class CPlayer : public CActor { static bool CanHit (CActor* pAttacker, CActor* pTarget); void Attack(CActor* pTarget); ... void CPlayer::Attack(CActor* pTarget) { if (CanHit(this, pTarget)) { pTarget->BeAttacked(this); } TEST_FIXTURE(FixtureBattle, PlayerCanHitMonster) { CHECK(CPlayer::CanHit(pPlayer, pMonster)); pPlayer->m_Level = 40; pMonster->m_Level = 80; CHECK(!CPlayer::CanHit(pPlayer, pMonster)); class CPlayer : public CActor { bool CanHit(CActor*); void Attack(CActor* pTarget); ... void CPlayer::Attack(CActor* pTarget) { if (CanHit(pTarget)) { pTarget->BeAttacked(this); } }
57. 예 : test 코드 실행 중 dead lock 이 걸려요 . class CPlayer : public CActor { CMutexLock m_Lock; // recursive lock 아님 void BeAttacked(CActor* pAttacker) { m_Lock.Lock(); pAttacker.Lock(); pAttacker->BeAttacked();
58. 해결 방법 class CMutexLockImpl : public ILock { void Lock() { WaitForSingleObject(...); class CDummyLockImpl : public ILock { void Lock() { // do nothing class CLockImp<typename T> { T m_Lock; void Lock() { m_Lock.Enter(); } }; typedef CLockImp<CMutexLockImpl> CMutexLock; typedef CLockImp<CDummyLockImpl> CDummyLock;
59. 더 할 수 있다 ! class CDummyLockImpl : public ILock { void Lock() { if (CDummyLockImpl::Inst().IsDeadLock()) { throw CException(); 테스트를 하면서 , deadlock 를 감지할 수 있다
75. class CTest{ protected: int m_Test; void Test() {} }; class CMockTest : public CTest { public: using CTest::m_Test; // 부모 클래스의 멤버를 public 으로 쓰겠다 . using CTest::Test; }; TEST(AccessTest) { CTest a; //a.m_Test = 1; // protected 멤버 변수 접근할 수 없음 . //a.Test(); // protected 멤버 함수 접근할 수 없음 . CMockTest* pMockTest = (CMockTest*)(&a); pMockTest->m_Test = 1; // CMockTest 로 강제 캐스팅 -> 접근 가능 pMockTest->Test(); // a 가 CMockTest 객체가 아니어도 이렇게 쓸 수 있다는 점에 주의 return 0; }