Наскільки я зрозумів питання стоїть не в тому нащо protected потрібен, а в тому де його можна використати. Приведу кілька прикладів де саме можна використати protected, всі вони взяті з класичних шаблонів програмування. В наведених нижче прикладах модифікатор доступу "protected" не є необхідним, але його використання є логічним та доречним, принаймні на мою думку. Поїхали!
Шаблон "Будівник"
Призначення:
Інкапсулює процес створення складного об’єкта для всіх підвидів цього об’єкта.
Ідея реалізації:
Для створення складного об’єкту Obj використовується спеціальний клас-будівник Builder, методи якого створюють якусь певну частину об’єкта Obj. Тобто якщо, наприклад, клас Obj це клас "Армія", то один метод Builder’а створює піхотинців, ще один лучників, інший квалерію, ну і ще якийсь - космічні винищувачі.
Процесом створення армії керує інший клас - Director. Директор приймає будівника як вхідний параметр і використовує його для створення армії, контролюючи процес створення. Тобто створив піхотинців, перевірив що все ок, створив лучників - перевірив... Якщо процес створення пройшов нормально то директор повертає створений об’єкт клієнту.
Оскільки армій може бути багато то сам клас "Армія" робиться як абстракний, що визначає інтерфейс взаємодії з армією, а вже від нього успадковуються різні армії - римська, грецька, єгипетська. Для кожної такої армії створюється свій будівник - тобто скільки армій стільки й будівників. Для того щоб всі будівники працювали однаково вони успадковуються від "верховного" будівника Builder, реалізуючи його інтерфейс.
Абстрактний будівник Builder містить в собі посилання на об’єкт армії, який він повертає після створення армії. Посилання є protected бо тільки успадковані від Builder’а будівники конкретних армій повинні мати доступ до об’єкту армії (оскільки вони її наповнюють). Всі інші об’єкти не повинні мати безпосередній доступ до армії, яку створює будівник.
Що це дає:
Клієнт, передаючи директору будівників різних армій, отримує різні армії, не володіючи інфомацією як саме вони створюються. Це дозволяє керувати складним процесом створення, добавляти нові підвиди об’єктів (нові армії в цьому випадку), не змінюючи код клієнта.
Приклад коду:
▼Прихований текст
#include <iostream>
using std::string;
using std::cout;
using std::cin;
using std::endl;
class Infantryman {
public:
string info() { return "Infantryman"; }
};
class Archer {
public:
string info() { return "Archer"; }
};
class Horseman {
public:
string info() { return "Horseman"; }
};
class Catapult {
public:
string info() { return "Catapult"; }
};
class Elephant {
public:
string info() { return "Elephant"; }
};
// Class includes all army units
class Army {
public:
Army() : infantryman( 0 ), archer ( 0 ), horseman( 0 ), catapult( 0 ), elephant( 0 ) {}
~Army();
Infantryman* infantryman;
Archer* archer;
Horseman* horseman;
Catapult* catapult;
Elephant* elephant;
string info() const ;
};
Army::~Army() {
if( infantryman != 0 ) delete infantryman;
if( archer != 0 ) delete archer;
if( horseman != 0 ) delete horseman;
if( catapult != 0 ) delete catapult;
if( elephant != 0 ) delete elephant;
}
string Army::info() const {
return ( ( infantryman != 0 ) ? infantryman->info() + '\n' : "" )
+ ( ( archer != 0 ) ? archer->info() + '\n' : "" )
+ ( ( horseman != 0 ) ? horseman->info() + '\n' : "" )
+ ( ( catapult != 0 ) ? catapult->info() + '\n' : "" )
+ ( ( elephant != 0 ) ? elephant->info() + '\n' : "" );
}
std::ostream& operator<< (std::ostream& out, const Army& army ) {
out << army.info();
return out;
}
class ArmyBuilder {
protected:
Army* p;
public:
ArmyBuilder() : p( 0 ) {}
~ArmyBuilder() {}
virtual void createArmy() {}
virtual void buildInfantryman() {}
virtual void buildArcher() {}
virtual void buildHorseman() {}
virtual void buildCatapult() {}
virtual void buildElephant() {}
virtual Army* getArmy() { return p; }
};
class RomeArmyBuilder : public ArmyBuilder {
public:
void createArmy() { p = new Army; }
void buildInfantryman() { p->infantryman = new Infantryman(); }
void buildArcher() { p->archer = new Archer(); }
void buildHorseman() { p->horseman = new Horseman(); }
void buildCatapult() { p->catapult = new Catapult(); }
};
class CarfagenArmyBuilder : public ArmyBuilder {
public:
void createArmy() { p = new Army; }
void buildInfantryman() { p->infantryman = new Infantryman(); }
void buildArcher() { p->archer = new Archer();}
void buildHorseman() { p->horseman = new Horseman(); }
void buildElephant() { p->elephant = new Elephant(); }
};
class ArmyDirector {
public:
Army* createArmy( ArmyBuilder& builder ) {
builder.createArmy();
builder.buildInfantryman();
builder.buildArcher();
builder.buildHorseman();
builder.buildCatapult();
builder.buildElephant();
return builder.getArmy();
}
};
void aBuilder() {
RomeArmyBuilder romeBuilder;
CarfagenArmyBuilder carfagenBuilder;
ArmyDirector dir;
Army* ra = dir.createArmy( romeBuilder );
Army* ca = dir.createArmy( carfagenBuilder );
cout << "Rome army:\n" << *ra << endl;
cout << "Carthaginian army:\n" << *ca << endl << endl;
delete ra;
delete ca;
}
int main() {
aBuilder();
return 0;
}
Де можна почитати:
Е. Гамма Р. Хелм Р. Джонсон Дж. Вліссдес - Прийоми ООП проектування. ISBN 5-272-00355-1 Стор. 102
Шаблон "Прототип"
Призначення:
Задає види створюваних об’єктів за допомогою екземпляра-прототипа и створює нові об’єкти шляхом копіювання прототипу.
Ідея реалізації:
Ідея прототипу полягає в тому що десь існує "реєстр" об’єктів різних класів, які може створювати клієнт. Зазвичай реєстр це структура даних виду map з ключем "идентифікатор типу класа" і значеням - "екземпляр цього класу".
Кожен екземпляр, що знаходиться в реєстрі, по суті є прототипом на основі якого створюються інші об’єкти - об’єкти створюютьсям шляхо копіювання екземпляра. Для цього всі класи що є чи можуть бути в реєстрі є нащадками деякого інтерфейсу Prototype. В цьому інтерфейсі оголошена операція копіювання об’єкта - clone(), реалізована операція створення об’єкта по ІД, та також є статичні функції додавання та видалення прототипів з реєстру - addProtopype та removePrototype.
Створення об’єкта відбувається наступним чином - в метод створення передається ІД класу і по ІД реєстрі шукається екземпляр цього класу. Якщо екземпляр знайдений, то з нього робиться копія і вона повертається як результат.
Кожен підклас класу Prototype має статичний екземпляр свого виду, який автоматично добавляється в реєстр при ініціалізації статичних змінних програми, за допомогою метода addProtopype (в даному). Оскільки методи addProtopype та removePrototype ніде не використовуються окрім класів-нащадків, то вони оголошені як protected.
Що це дає:
Клієнт не залежить від виду об’єктів які він може створювати. Можна добавляти нові підвиди об’єктів, не змінюючи код клієнта, цей процес може бути навіть динамічним.
Приклад коду:
▼Прихований текст
#include <iostream>
#include <map>
#include <vector>
using std::string;
using std::cout;
using std::cin;
using std::endl;
enum Warrior_ID {Infantryman_ID, Archer_ID, Horseman_ID };
class Warrior; // forward declaration
typedef std::map< Warrior_ID, Warrior* > Registry;
Registry& getRegistry() {
static Registry instance;
return instance;
}
class Dummy{};
class Warrior {
public:
virtual Warrior* clone() = 0;
virtual string info() = 0;
virtual ~Warrior() {}
static Warrior* createWarrior( Warrior_ID id ) {
Registry& r = getRegistry();
if( r.find(id) != r.end() )
return r[id]->clone();
return 0;
}
protected:
static void addProtopype( Warrior_ID id, Warrior* prototype) {
Registry& r = getRegistry();
r[id] = prototype;
}
static void removePrototype( Warrior_ID id ) {
Registry& r = getRegistry();
r.erase( r.find( id ) );
}
};
class Infantryman : public Warrior {
public:
Warrior* clone() {
return new Infantryman( * this );
}
string info() {
return "Infantryman";
}
private:
Infantryman( Dummy ) {
Warrior::addProtopype( Infantryman_ID, this );
}
Infantryman() {}
static Infantryman prototype;
};
class Archer : public Warrior {
public:
Warrior* clone() {
return new Archer( * this );
}
string info() {
return "Archer";
}
private:
Archer( Dummy ) {
Warrior::addProtopype( Archer_ID, this );
}
Archer() {}
static Archer prototype;
};
class Horseman : public Warrior {
public:
Warrior* clone() {
return new Horseman( * this );
}
string info() {
return "Horseman";
}
private:
Horseman( Dummy ) {
Warrior::addProtopype( Horseman_ID, this );
}
Horseman() {}
static Horseman prototype;
};
Infantryman Infantryman::prototype = Infantryman( Dummy() );
Archer Archer::prototype = Archer( Dummy() );
Horseman Horseman::prototype = Horseman( Dummy() );
void aPrototype() {
std::vector< Warrior* > v;
v.push_back( Warrior::createWarrior( Infantryman_ID ) );
v.push_back( Warrior::createWarrior( Archer_ID ) );
v.push_back( Warrior::createWarrior( Horseman_ID ) );
for( size_t i = 0; i < v.size(); ++i )
cout << v[i]->info() << endl;
}
int main() {
aPrototype();
return 0;
}
З.І. Це один з варіантів реалізації шаблона прототип. Є ще варіант в якому прототипи зберігаються не в реєстрі, а в спеціальній "фабриці прототипів".
Де можна почитати:
Е. Гамма Р. Хелм Р. Джонсон Дж. Вліссдес - Прийоми ООП проектування. ISBN 5-272-00355-1 Стор. 121
Я нарахував використання protected в 9 шаблонах проектування, з 22.
Надіюсь, що комусь буде цікаво почитати цю писанинну.