Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Обсуждение шаблонов проектирования (стереотипы), связь между кодом C++ и проектированием 
:(
    Опции темы
atomicxp
  Дата 13.6.2009, 12:45 (ссылка) |    (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



Цитата(math64 @  13.6.2009,  11:36 Найти цитируемый пост)
Если вы хотите запретить удалять объект через интерфейс, объявите деструктор в секции protected:

Очень хороший совет:

Код
#include <iostream>

using namespace std;

class InterfaceA
{
 protected:
    virtual ~InterfaceA() = 0;
 public:
    virtual void ma() = 0;
    virtual void mb() = 0;
};

class ClassA : public InterfaceA
{
 protected:
    ~ClassA(){ cout << "Desctruction ClassA" << endl; }
 public:
    void ma()
    {
        cout << "ClassA: Method A" << endl;
    }
    void mb()
    {
        cout << "ClassA: Method B" << endl;
    }
};

void Testing(ClassA* classA)
{
    InterfaceA* interfaceA;

    interfaceA = classA;

    interfaceA->ma();
    interfaceA->mb();

    delete interfaceA; // |38|ошибка: в данном контексте|
}

int main()
{
    ClassA* classA = new ClassA();

    Testing(classA);

/*    classA->ma();
    classA->mb();

    delete classA;*/

    return 0;
}


Цитата(компилятор)
/media/Data_01/Work/Start/AxCoreProjects/InterfaceNix/main.cpp||In function ‘void Testing(ClassA*)’:|
/media/Data_01/Work/Start/AxCoreProjects/InterfaceNix/main.cpp|8|ошибка: ‘virtual InterfaceA::~InterfaceA()’ is protected|
/media/Data_01/Work/Start/AxCoreProjects/InterfaceNix/main.cpp|38|ошибка: в данном контексте|
||=== Build finished: 2 errors, 0 warnings ===|


И ещё я подумал, может стоит сделать виртуальный деструктор чисто абстрактным. Все равно ведь не используется в силу определения интерфейса. Над этим стоит поразмышлять.

Добавлено через 8 минут и 49 секунд
P.S. хотя, конечно, виртуальный деструктор не нужен, лишняя виртуальная функция.

Добавлено через 11 минут и 40 секунд
То есть виртуальный деструктор нужен, не нужен чисто абстрактный виртуальный деструктор. smile

Добавлено через 12 минут и 45 секунд
Но если он уже виртуальный, то без разницы, чисто абстрактный он или нет. (специально мысли свои не стираю)
PM MAIL WWW Skype GTalk Jabber   Вверх
atomicxp
  Дата 13.6.2009, 13:18 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



Значит так, виртуальный деструктор нужен для предотвращения утечек памяти в случае удаления объекта  класса, который в свою очередь использовал интерфейс, через указатель на интерфейс, который указывает на объект класса использующий интерфейс.

Если кратко, то в случае защищённого деструктора удаление через него невозможно, следовательно утечка памяти тоже невозможна, а значит виртальный деструктор не нужен.

Код
class InterfaceA
{
 protected:  // запрещает удалять derived объект через интерфейс
    ~InterfaceA();
 public:
    virtual void ma() = 0;
    virtual void mb() = 0;
};


Добавлено через 5 минут и 52 секунды
У меня
Код
    ~InterfaceA(); // объявление деструктора


У math.
Код
    ~InterfaceA(){} // объявление и определение деструктора


Судя по тому, что компилятор выдаёт одни и те же ошибки, даже в пустом определении деструктора нет необходимости.
PM MAIL WWW Skype GTalk Jabber   Вверх
mes
Дата 13.6.2009, 13:34 (ссылка) |    (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 144
Всего: 250



Цитата(atomicxp @  13.6.2009,  11:45 Найти цитируемый пост)
И ещё я подумал, может стоит сделать виртуальный деструктор чисто абстрактным.

У создаваемого объекта, каждый деструктор (его и предков) должен иметь тело. Т.е абсолютно чистым его не сделать. 
Код

class A { virtual ~A()=0; }; // <- ставим "метку чистоты".
A:~A() {} //<- обязательное тело.


Цитата(atomicxp @  13.6.2009,  12:18 Найти цитируемый пост)
Если кратко, то в случае защищённого деструктора удаление через него невозможно, следовательно утечка памяти тоже невозможна, а значит виртальный деструктор не нужен.

Это вырожденный случай и на практике такое не случается, к тому же обладает скрытым недостатком. (Надо всегда помнить, что у унаследованного от наследника (у которого деструктор будет открыт) такого класса нельзя будет пользоваться полиморфным удалением, или придется все равно добавлять виртуальный деструктор)

А стоит ли экономить место под один указатель_на_метод на класс (а не один на объект) ? и из за этой мнимой экономии обрекать себя на скрытые ошибки и неудобства ?

Добавлено @ 13:37
Цитата(atomicxp @  13.6.2009,  12:18 Найти цитируемый пост)
Судя по тому, что компилятор выдаёт одни и те же ошибки, даже в пустом определении деструктора нет необходимости. 

Попробуйте создать объект отнаследованного класса  smile

Это сообщение отредактировал(а) mes - 13.6.2009, 15:35


--------------------
PM MAIL WWW   Вверх
atomicxp
  Дата 13.6.2009, 14:30 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



В общем, в интерфейсе есть необходимость в пустом блоке объявлении деструктора для удаления объекта использующего в классе интерфейс. Пожалуй на этом и всё, пора переходить к следующим шаблонам проектирования.
PM MAIL WWW Skype GTalk Jabber   Вверх
atomicxp
  Дата 13.6.2009, 16:55 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



Переходим к следующему шаблону проектирования так же принадлжащему к типу основных шаблонов (Fundamental)

    * Delegation pattern/Шаблон делегирования

Оригинальничать не будем, потому сразу определение из вики:

Цитата(wiki)
В разработке ПО, шаблон делегирования (англ. delegation pattern) — это способ, которым объект внешне выражает некоторое поведение, но в реальности передаёт ответственность за выполнение этого поведения связанному объекту. Шаблон делегирования является фундаментальной абстракцией которая поддерживает композицию (также называемую агрегацией), примеси (mixins) и аспекты (aspects).


Та же вики сообщает о минусах
Цитата(wiki)
Этот шаблон обычно затрудняет оптимизацию по скорости в пользу улучшенной чистоты абстракции.


Плюсы и применимость не указана, но это и не важно, потому что данный шаблон наиболее часто используемый, так что не требует особых объяснений. Но раз уж я решил обсудить шаблоны, то придётся сделать это и для него.

Как уже было сказано, шаблон делегирования, так же известный как техника программирования включение-делегирование, ухудшает чистоту абстракций. Но здесь так же стоит отметить, что подобное негативное влияние можно сократить, а можно наоборот увеличить этот эффект. Под увеличением подразумеваются смешанные классы. Они очень сильно вредят чётким абстракциям.

Плюс заключается в возможности перестроения чужих алгоритмов под свои собственные. Таким образом можно перенести целые наборы библиотек. Именно так был построен .NET Framework, ведь рефлектор наглядно показывает, что шаблон делегирования используется в нём чуть ли не чаще, чем всё остальное. Аналогичным образом строится Mono, непроделегированные методы можно просто объявить неиспользуемыми.

Если проследить рефлектором историю изменения, то от версии 1.1 к версии 2.0 данная техника программирования стала использоваться для ухудшения абстракций самого дотнета. Хотя речь совсем не о том, в какую сторону двигается майкрософт, а о C++. Простейший пример делегирования:

Код
#include <iostream>

using namespace std;

class ClassA
{
 public:
    void ma()
    {
        cout << "ClassA: Method A" << endl;
    }
    void mb()
    {
        cout << "ClassA: Method B" << endl;
    }
};

class ClassB
{
 private:
    ClassA* classA;
 public:
    ClassB()
    {
        classA = new ClassA();
    }

    void ma()
    {
        classA->ma();
    }
    void mb()
    {
        classA->mb();
    }
};

void Testing()
{
    ClassA* classA = new ClassA();

    classA->ma();
    classA->mb();

    ClassB* classB = new ClassB();

    classB->ma();
    classB->mb();
}

int main()
{
    Testing();

    return 0;
}


А вот более интересное использование из вики:

Код
#include <iostream>

using namespace std;

class I {
   public:
      virtual void f ( void ) = 0;
      virtual void g ( void ) = 0;
};

class A : public I {
   public:
      void f ( void ) { cout << "A: вызываем метод f()" << std::endl; }
      void g ( void ) { cout << "A: вызываем метод g()" << std::endl; }
};

class B : public I {
   public:
      void f ( void ) { cout << "B: вызываем метод f()" << std::endl; }
      void g ( void ) { cout << "B: вызываем метод g()" << std::endl; }
};

class C : public I {
   public:
     // Конструктор
      C() : i ( new A() ) { }
     // Деструктор
      virtual ~C() {
         delete i;
      }
      void f ( void ) { i -> f(); }
      void g ( void ) { i -> g(); }
     // Этими методами меняем поле-объект, чьи методы будем делегировать
      void toA ( void ) {
         delete i;
         i = new A();
      }
      void toB ( void ) {
         delete i;
         i = new B();
      }
   private:
     // Объявляем объект методы которого будем делегировать
      I * i;
};

int main ( void ) {
   C * c = new C();

   c -> f();
   c -> g();
   c -> toB();
   c -> f();
   c -> g();

   delete c;
   return 0;
}


Данный пример намного более интересен, создаются несколько классов и благодаря переключению с помощью интерфейса появляется возможность использовать в одном объекте одними методами совершенно разную реализацию.

Конечно, недостаток в том, что приходится переключать весь класс. Так же подобных результатов можно добиться указателями на функции, а не только интерфейсами. Как бы то ни было к шаблону делегирования это не относится.
PM MAIL WWW Skype GTalk Jabber   Вверх
Курсант
Дата 13.6.2009, 17:19 (ссылка)  | (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 338
Регистрация: 21.2.2009
Где: Балашиха или Воро неж

Репутация: нет
Всего: 4



Чувак, продай мне этой травы, а smile Вот это тебя проперло smile

Добавлено через 1 минуту и 6 секунд
И не отпускает.. *грустнеет* теперь заминусуют...
PM ICQ Skype   Вверх
mes
Дата 13.6.2009, 17:28 (ссылка)    | (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 144
Всего: 250



Цитата(atomicxp @  13.6.2009,  15:55 Найти цитируемый пост)
тот шаблон обычно затрудняет оптимизацию по скорости в пользу улучшенной чистоты абстракции.

Цитата(atomicxp @  13.6.2009,  15:55 Найти цитируемый пост)
ухудшает чистоту абстракций.

так ухудшает или улучшает ?




--------------------
PM MAIL WWW   Вверх
azesmcar
Дата 13.6.2009, 17:29 (ссылка) |    (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



atomicxp
Цитата(atomicxp @  13.6.2009,  16:55 Найти цитируемый пост)
А вот более интересное использование из вики:

Похоже на смесь с контуженным патерном State.
PM   Вверх
atomicxp
  Дата 13.6.2009, 19:56 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



Далее по плану следует Immutable/Неизменяемый объект. Для начала нужно сказать, что неизменяемость объекта достигается различными путями. Как только объект был инициализирован, он уже не должен изменятся с точки зрения пользователя этого объекта, и не важно, чем это выражено. Объект может быть как частично, так и полностью незименямым. Иногда это достигается ключевым словом const, в других случаях закрытыми полями и методом доступа с запретом изменять значения объекта.

Простейший случай использования:
Код
#include <iostream>

using namespace std;

class ClassA
{
 private:
    int _value; // immutable field
 public:
    ClassA(const int& value)
    {
        _value = value;
    }
 public:
    int getValue() const // immutable using
    {
        return _value;
    }
};

void Testing()
{
    ClassA* classA = new ClassA(15);
    cout << "value = " << classA->getValue() << endl;
}

int main()
{
    Testing();

    return 0;
}


Неизменямость хоть и проста в использовании, но требует более детального рассмотрения.
PM MAIL WWW Skype GTalk Jabber   Вверх
atomicxp
  Дата 13.6.2009, 21:17 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



Рассмотрим для чего же выделять шаблоны проектирования, и как это поможет в реальном деле. Если бы шаблоны проектирования были слишком сильно размыты и их нельзя выделить в стереотипы, нам бы незачем было о них говорить. Взглянем на UML отображение класса, которое я сделал.

user posted image

Стереотипы традиционно выделяются в <<>>, а # означает закрытый. Стереотип <<Immutable>> в данном случае отнёс вовсе не к классу, а к методу. Иными словами шаблоны проектирования бывают разными и относятся к разным частям класса.

Поскольку уже было рассмотрено три шаблона, то возникает вопрос, можно ли комбинировать какие-либо из них. Если взять тот же интерфейс, то его стереотип был бы записан наверху, заместо слова <<stereotype>> находилось бы <<Interface>>.

А вот шаблон делегирования и неизменяемости относятся к методам, особенно если исходить, что включённые (агрегированные) объекты брать всегда используя методы, а сами объекты делать закрытыми или защищёнными. Иными словами пользоваться свойствами (присвоение или извлечение объекта с проверкой), ведь даже там где они имеют другой вид, например, .NET, на самом деле создаются методы с приставкой get/set.

Может ли в методе быть <<immutable delegation>>, очевидно да. Даже в таких размытых шаблонах проектирования можно добиться определённости. Причём как видно, некоторые шаблоны этих же категорий, а так же многих других, имеют более чёткое определение и вообще не комбинируются вместе.
PM MAIL WWW Skype GTalk Jabber   Вверх
Курсант
Дата 14.6.2009, 15:05 (ссылка) |   (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 338
Регистрация: 21.2.2009
Где: Балашиха или Воро неж

Репутация: нет
Всего: 4



Знал я одного атомика из Удмуртии... Жека, это не ты случайно? smile

Добавлено через 53 секунды
Тоже атомик.. Тоже из удмуртии.. Тоже программер великий..
PM ICQ Skype   Вверх
unicuum
  Дата 14.6.2009, 16:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 830
Регистрация: 16.3.2005
Где: Рашка

Репутация: 1
Всего: 8



Предыдущая тема. smile 

Это сообщение отредактировал(а) unicuum - 14.6.2009, 16:16


--------------------
user posted image
обычный день на винграде
PM   Вверх
atomicxp
  Дата 14.6.2009, 16:13 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 58
Регистрация: 2.5.2009
Где: Удмуртия, Ижевск

Репутация: 1
Всего: 1



Цитата(Курсант @  14.6.2009,  15:05 Найти цитируемый пост)
Знал я одного атомика из Удмуртии... Жека, это не ты случайно? smile

Добавлено через 53 секунды
Тоже атомик.. Тоже из удмуртии.. Тоже программер великий.. 

Нет, не я.  smile 

Ладно, надоели мне эти фундаментальные шаблоны, к тому же никому они неинтересны, сейчас скопом их опишу и дальше двинусь.

Основные шаблоны (Fundamental)

    * Delegation pattern/Шаблон делегирования
    * Functional design/Шаблон функционального дизайна
    * Immutable/Неизменяемый объект
    * Interface
    * Marker interface
    * Property Container

Property Container/Контейнер свойств

Ниже представлен простейший пример с закрытым значением поля, который хранит свойство. Два метода, один называется accessor, имеет приставку get и извлекает значение. Другой называется mutator, имеет приставку set и изменяет значение.

Код
#include <iostream>

using namespace std;

class ClassA
{
 private:
    int _value; // property field
 public:
    ClassA()
    {
        _value = 0;
    }
 public:
    int getValue() const // accessor
    {
        // get property
        return _value;
    }
    void setValue(const int& value) // mutator
    {
        // check & assignment property
        _value = value;
    }
};

int main()
{
    ClassA* classA = new ClassA();
    cout << classA->getValue() << endl;
    classA->setValue(15);
    cout << classA->getValue() << endl;

    return 0;
}


Так же стоит отметить, что в реальности данные могут хранится где угодно, не обязательно в закрытом поле, и быть в любом виде. За их проверку и перобразование отвечает accessor и mutator, в простейшем случае я не стал использовать дополнительные возможности, которые они предоставляют. В сложных случаях могут использоватся инструкции ветвления, а так же выбрасываться исключения.

Это сообщение отредактировал(а) atomicxp - 14.6.2009, 16:25
PM MAIL WWW Skype GTalk Jabber   Вверх
unicuum
  Дата 16.6.2009, 13:33 (ссылка) |  (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 830
Регистрация: 16.3.2005
Где: Рашка

Репутация: 1
Всего: 8



Даже не хочу это комментировать:

Цитата
* Functional design/Шаблон функционального дизайна
* Marker interface/Разметочный интерфейс


Шаблон функционального дизайна гласит, что класс должен отвечать за минимальную область деятельности. Любители смешанных классов (миксов) нарушают этот фундаментальный принцип, уменьшая степень абстракции.

В конце концов если смешанные классы перерастают в божественные, ни о какой абстракции системы говорить не приходится. Такую технику программирования можно воспринимать как псевдо объекто-ориентированное программирование.

Существует ряд рекомендованных правил для этого шаблона. Одно из них правило семи - в классе нужно стараться держать не более семи элементов. Контейнер свойств из двух методов можно воспринимать как один элемент. Даже если они ссылаются на включённый (агрегированный) класс, это все равно не три элемента, а один.

Если элементов стало больше семи, то это не значит, что нужно немедленно начать всё делить. Это значит, что надо задуматься над реализацией, возможно в ней что-то неправильно, и она нуждается в перепроектировании. Так же стоит следить за чистотой реализации, не допускать чрезмерного смешения шаблонов проектирования, которые для этого не предназначены.

Разметочный интерфейс позволяет получить дополнительные данные о классах, то есть метаданные (данные о данных). В некоторых системах текущий шаблон проектирования известен как рефлексия. Реализовывается различными способами, например, атрибутами.



--------------------
user posted image
обычный день на винграде
PM   Вверх
Леопольд
Дата 17.6.2009, 15:19 (ссылка) |   (голосов:6) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Hi atomicxp,

Почему-то у меня складывается ощущение что Вам стоит перечитать Страуструпа и прочитать Скота Мейерса "55 способов...", хотя бы. Могу аргументировать это только тем, что Ваши посты могли бы быть больше связанными со спецификой С++. Иногда, складывается ощущение что Вы не совсем понимаете как работает С++. К примеру, в целях проектирования на С, можно использовать разницу между статической и динамической компоновкой/линковкой обычных функций для имитации инкапсуляции. В С++ есть поиск функции по аргументу, уверен тоже можно куда-нибудь присобачить... smile А шаблоны, это отдельный С++ на изучение которых можно потратить гораздо больше времени чем на всё остальное вместе взятое. Очень мощная штука. Советую почитать:
1. "Шаблоны. Справочник разработчика" David Vandevoorde, Nicolai M. Josuttis
2. "Современное проектирование на С++" Александреску (именно в этом порядке)
Я сам ещё 1-ю не прочитал, но уже понял что надо обязательно дочитать...


Если же ближе к теме, то моё мнение о паттернах проектирования следующее:

1. Прочитать и понять идеи опытных разработчиков, спроектировавших с их помощью серьёзный и крупный софт, очень полезно.

2. Зная о смысле паттернов, можно применять их на практике хоть на ассемблере, всё зависит от того, насколько сильно Вы готовы абстрагироваться от конструкций языка, которые вы будете называть реализацией паттерна.

3. Пользоваться чужими идеями нужно обдуманно, а совсем не пользоваться своими просто глупо. Уверен, что с этим согласится человек любой профессии. Показатель способностей разработчика софта - уметь решать подобные головоломки. Уверен многим именно поэтому нравится своя работа.


По этим причинам я думаю что нет особого смысла пытаться реализовать каждый паттерн на каком либо языке. Если знаешь язык то это не составит труда, главное знать что хочешь реализовать. К тому же, следует учесть что иногда приёмы программирования устаревают или хуже того, встраиваются в синтаксис языка. smile Более того, можно, например, придумать паттерн который имеет смысл только в совокупности с другими паттернами а самостоятельная реализация будет абсолютно бессмысленна.

Это сообщение отредактировал(а) Леопольд - 17.6.2009, 15:30


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1090 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.