Практическое наследование (Рейтинг: +1)
Введение
Раньше мне приходилось довольно часто сталкиваться с проблемой непонимания начинающими программистами основ такого важного механизма ООП, как наследование. Задачи, поставленные мной, часто приводили к созданию такого необычного решения, что приходилось переписывать добрую часть кода, не смотря на то, что частично решение уже было реализовано ранее. Мне удалось решить эту проблему в своем коллективе и сейчас я хочу поделиться с вами секретом "наследования".
Целью данной статьи является демонстрация использования наследования на реальных примерах. Возможно, пример окажется слишком узким, но, как мне кажется, он достаточен для внимательного читателя.
Поставленная задача
Как-то раз мне потребовалась реализация механизма аннотирования классов и их членов как в Java. Если кто не знает, это возможность присвоения дополнительных данных классам, их свойствам, методам, аргументам методов и т.д. К примеру, если нам требуется записать объект в базу данных, то необходимо связать свойства объекта с полями таблицы, а так же указать, в какую таблицу необходимо записать данный класс. В моем случае это должно быть реализовано следующим образом:
Данная задача была поставлена мной одному из моих программистов и дана неделя для разработки, реализации и тестирования.
Неудачное решение
Решение, предложенное моим программистом, меня несколько удивило. Оно выглядело следующим образом: был разработан интерфейс Described который позволял добавлять метаданные к любому, реализующему его классу
- Type - представление класса;
- Property - представление свойства;
- Method - представление метода.
Все эти классы являлись подклассами класса ClassMember который реализовывал интерфейс Described, следовательно, все эти классы могли быть описаны с помощью метаданных.
Мой программист так же создал интерфейс Reflect, реализация которого позволяла получить соответствующие отображения:
Разбор полетов
На первый взгляд может показаться, что код вполне приличен, в нем есть и ООП, и инкапсуляция, и полиморфизм и даже наследование, но на деле это не так. В действительности представления, написанные моим программистом: Type, Method, Property - уже существуют в стандартной библиотеке PHP и называются ReflectionClass, ReflectionMethod и ReflectionProperty. Существуют и дополнительные представления, реализованные в стандартной библиотеке и отсутствующие в данной реализации, а так же эти стандартные решения имеют дополнительные методы, которые придется реализовать нам самостоятельно. Пропадает повторное использование кода!
Если вы хорошо знакомы с ОО моделью в PHP, то знаете сколько полезных классов она включает, потому попробуем использовать существующее решение и решить задачу с помощью наследования.
Более хорошее решение
Мы не отказались от некоторых решений, предложенных моим программистом, в частности мы оставили интерфейс Described, его Trait, а так же Reflect и его Trait, так как аналога в стандартных решениях у нас нет. Мы выбросили классы Type, Method, Property и воспользовались следующими подклассами:
Посмотрите, как сейчас реализован механизм получения отображений:
Посмотрите, как изменится код:
Заключение
Запомните, если ваша задача требует использование новых классов, то окиньте взглядом уже существующие решения, возможно решить проблему можно использую наследование.
Старайтесь писать код не просто рабочим, а удобным в сопровождении. Удачи в программировании!
Автор: Артур (25.06.2012 / 20:52)
Просмотры: 1024
Комментарии (1) »
Раньше мне приходилось довольно часто сталкиваться с проблемой непонимания начинающими программистами основ такого важного механизма ООП, как наследование. Задачи, поставленные мной, часто приводили к созданию такого необычного решения, что приходилось переписывать добрую часть кода, не смотря на то, что частично решение уже было реализовано ранее. Мне удалось решить эту проблему в своем коллективе и сейчас я хочу поделиться с вами секретом "наследования".
Целью данной статьи является демонстрация использования наследования на реальных примерах. Возможно, пример окажется слишком узким, но, как мне кажется, он достаточен для внимательного читателя.
Поставленная задача
Как-то раз мне потребовалась реализация механизма аннотирования классов и их членов как в Java. Если кто не знает, это возможность присвоения дополнительных данных классам, их свойствам, методам, аргументам методов и т.д. К примеру, если нам требуется записать объект в базу данных, то необходимо связать свойства объекта с полями таблицы, а так же указать, в какую таблицу необходимо записать данный класс. В моем случае это должно быть реализовано следующим образом:
<?phpКласс, аннотированный данным образом, использует таблицу PeopleTable для записи своих объектов, а его свойства записываются в поля firstNameField, nameField, midleNameField, dateBirthField. Другими словами, аннотация позволяет добавлять метаданные (данные описывающие данные) в код.
/*
TableName=PeopleTable
*/
class People{
/*
FieldName=firstNameField
*/
private $firstName;
/*
FieldName=nameField
*/
private $name;
/*
FieldName=midleNameField
*/
private $midleName;
/*
FieldName=dateBirthField
*/
private $dateBirth;
...
publid getFirstName(){
return $this->firstName;
}
...
}
Данная задача была поставлена мной одному из моих программистов и дана неделя для разработки, реализации и тестирования.
Неудачное решение
Решение, предложенное моим программистом, меня несколько удивило. Оно выглядело следующим образом: был разработан интерфейс Described который позволял добавлять метаданные к любому, реализующему его классу
<?phpТак же были реализованы представления всех описываемых метаданными элементов класса:
interface Described{
// Метод возвращает все метаданные данного элемента.
public function getAllMetadata();
// Метод возвращает значение конкретных метаданных элемента.
public function getMetadata($metadataName);
// Метод устанавливает значение метаданных.
public function setMetadata($metadataName, $metadataValue);
// Метод проверяет, существуют ли заданные метаданные в вызываемом представлении.
public function isMetadataExists($metadataName);
}
- Type - представление класса;
- Property - представление свойства;
- Method - представление метода.
Все эти классы являлись подклассами класса ClassMember который реализовывал интерфейс Described, следовательно, все эти классы могли быть описаны с помощью метаданных.
Мой программист так же создал интерфейс Reflect, реализация которого позволяла получить соответствующие отображения:
<?phpДля ясности приведу пример кода, использующий аннотирование с решением, предложенным моим программистом:
interface Reflect{
// Метод возвращает представление свойства класса.
static public function &getReflectionProperty($propertyName);
// Метод возвращает представление метода класса.
static public function &getReflectionMethod($methodName);
// Метод возвращает представление класса.
static public function &getReflectionClass();
// Метод возвращает отображения всех свойств класса.
static public function getAllReflectionProperties();
// Метод возвращает отображения всех методов класса.
static public function getAllReflectionMethods();
}
<?php
// Анотируемый класс
class TestMetadata implement Reflect{
use TReflect;
private $var1;
private $var2;
public function method(){}
}
// Получаем представление переменной. Представление хранит только информацию о переменной, но не ее значение, потому инкапсуляция не нарушается.
$var1Reflect = TestMetadata::getReflectionProperty('var1');
// Добавляем аннотацию к свойству
$var1Reflect->setMetadata('FieldName', 'var1Field');
/* Теперь свойство var1 класса TestMetadata имеет аннотацию FieldName со значением var1Field, это позволит нам в будущем записать значение данного свойства в требуемое поле таблицы. Если изменится имя поля таблицы, достаточно будет изменить аннотацию */
Разбор полетов
На первый взгляд может показаться, что код вполне приличен, в нем есть и ООП, и инкапсуляция, и полиморфизм и даже наследование, но на деле это не так. В действительности представления, написанные моим программистом: Type, Method, Property - уже существуют в стандартной библиотеке PHP и называются ReflectionClass, ReflectionMethod и ReflectionProperty. Существуют и дополнительные представления, реализованные в стандартной библиотеке и отсутствующие в данной реализации, а так же эти стандартные решения имеют дополнительные методы, которые придется реализовать нам самостоятельно. Пропадает повторное использование кода!
Если вы хорошо знакомы с ОО моделью в PHP, то знаете сколько полезных классов она включает, потому попробуем использовать существующее решение и решить задачу с помощью наследования.
Более хорошее решение
Мы не отказались от некоторых решений, предложенных моим программистом, в частности мы оставили интерфейс Described, его Trait, а так же Reflect и его Trait, так как аналога в стандартных решениях у нас нет. Мы выбросили классы Type, Method, Property и воспользовались следующими подклассами:
<?phpЭти три класса расширяют стандартные классы PHP для работы с отображениями и дополняются новым функционалом - возможностью аннотирования.
class ReflectionClass extends \ReflectionClass implements \PPHP\patterns\metadata\Described{
use \PPHP\patterns\metadata\TDescribed;
}
class ReflectionMethod extends \ReflectionMethod implements \PPHP\patterns\metadata\Described{
use \PPHP\patterns\metadata\TDescribed;
}
class ReflectionProperty extends \ReflectionProperty implements \PPHP\patterns\metadata\Described{
use \PPHP\patterns\metadata\TDescribed;
}
Посмотрите, как сейчас реализован механизм получения отображений:
<?phpКак можно заметить, класс будет возвращать одно и то же отображения для одного свойства, а не несколько. Это позволяет сохранить аннотации.
trait TReflect{
// Отражение класса.
static private $reflectionClass;
// Множество отражений свойств класса.
static private $reflectionProperties;
// Множество отражений методов класса.
static private $reflectionMethods;
// Метод возвращает представление свойства класса.
static public function &getReflectionProperty($propertyName){
if(!is_string($propertyName) || empty($propertyName) || !property_exists(get_class(), $propertyName)){
throw new \InvalidArgumentException();
}
if(empty(self::$reflectionProperties)){
self::$reflectionProperties = [];
}
if(!array_key_exists($propertyName, self::$reflectionProperties)){
self::$reflectionProperties[$propertyName] = new ReflectionProperty(get_class(), $propertyName);
}
return self::$reflectionProperties[$propertyName];
}
// Метод возвращает представление метода класса.
static public function &getReflectionMethod($methodName){
if(!is_string($methodName) || empty($methodName) || !method_exists(get_class(), $methodName)){
throw new \InvalidArgumentException();
}
if(empty(self::$reflectionMethods)){
self::$reflectionMethods = [];
}
if(!array_key_exists($methodName, self::$reflectionMethods)){
self::$reflectionMethods[$methodName] = new ReflectionMethod(get_class(), $methodName);
}
return self::$reflectionMethods[$methodName];
}
// Метод возвращает представление класса.
static public function &getReflectionClass(){
if(empty(self::$reflectionClass)){
self::$reflectionClass = new ReflectionClass(get_class());
}
return self::$reflectionClass;
}
// Метод возвращает отображения всех свойств класса.
static public function getAllReflectionProperties(){
$reflectionProperties = new \SplObjectStorage();
$namesAllProperties = get_class_vars(get_class());
foreach($namesAllProperties as $k => $v){
$reflectionProperties->attach(self::getReflectionProperty($k));
}
return $reflectionProperties;
}
// Метод возвращает отображения всех методов класса.
static public function getAllReflectionMethods(){
$reflectionMethods = new \SplObjectStorage();
$namesAllMethods = get_class_methods(get_class());
foreach($namesAllMethods as $v){
$reflectionMethods->attach(self::getReflectionMethod($v));
}
return $reflectionMethods;
}
}
Посмотрите, как изменится код:
<?phpКак можно заметить, код не изменился вообще. Дело в том, что правильная архитектура пакета позволила нам инкапсулировать изменения в классах, потому код, использующий данные классы не претерпел изменений, но зато у нас отпала необходимость реализовывать три дополнительных класса и один абстрактный класс. Все благодаря наследованию!
// Аннотируемый класс
class TestMetadata implement Reflect{
use TReflect;
private $var1;
private $var2;
public function method(){}
}
// Получаем представление переменной. Представление хранит только информацию о переменной, но не ее значение, потому инкапсуляция не нарушается.
$var1Reflect = TestMetadata::getReflectionProperty('var1');
// Добавляем аннотацию к свойству
$var1Reflect->setMetadata('FieldName', 'var1Field');
/* Теперь свойство var1 класса TestMetadata имеет анотацию FieldName со значением var1Field, это позволит нам в будущем записать значение данного свойства в требуемое поле таблицы. Если изменится имя поля таблицы, достаточно будет изменить аннотацию */
Заключение
Запомните, если ваша задача требует использование новых классов, то окиньте взглядом уже существующие решения, возможно решить проблему можно использую наследование.
Старайтесь писать код не просто рабочим, а удобным в сопровождении. Удачи в программировании!
Автор: Артур (25.06.2012 / 20:52)
Просмотры: 1024
Комментарии (1) »