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

Здесь и далее я буду рассматривать общекнижный пример с сотрудниками предприятия, писать будем на чем-то СИ-подобном. Наследовать класс Сотрудник (Employee) от класса Человек (Person) – прекрасная идея, особенно если хранить данные исключительно в памяти: SQL имеет некоторые проблемы с наследованием таблиц, но речь не об этом — ООП со своим иерархизмом, агрегациями, композициями и наследованиями предлагает идеальный способ организации данных. Проблемы с методами.

За каждым методом бизнес-логики стоит факт мира, который этот метод (чаще не в одиночку) моделирует. Факты программирования – это операции: дальше будем называть их так. Делая метод членом класса, ООП требует от нас привязать операцию к объекту, что невозможно, потому что операция – это взаимодействие объектов (двух и более), кроме случая унарной операции, чистой рефлексии. Метод ВыдатьЗарплату (PaySalary) может быть отнесен к классам Сотрудник (Employee), Касса (Cash), БанковскийСчет (Account) – все они равнозначны в праве владения им. Дилемма о расположении методов сопутствует всему процессу разработки: неловкое ее разрешение может оказаться критичным и даже фатальным.

В книгах по программированию честные авторы стыдливо признают, что «объекты – это как бы не совсем объекты», а ООП – всего лишь способ организации кода, а не механизм моделирования. Но все дело том, что «мир есть совокупность фактов, а не вещей» – отсюда принципиальная неспособность построить адекватную модель, применяя ООП в том виде, как этого требуют писатели учебников. Важно понять: в коде возможно моделировать мир, но атомами модели должны стать факты, а не объекты.

Оказавшись два года назад в мире разработки ПО, я с ужасом осознал, что тут до сих пор царит Аристотель: ООП – прямое порождение его философии. Этот одиозный мыслитель придумал флогистон для химиков, движущую силу для физиков – да что там говорить! — приложился к каждой из крупных дисциплин. История европейского прогресса – это история преодоления Аристотеля. Науке он принес больше зла, чем вся Святая инквизиция. Две тысячи лет потребовалось нашим ученым, чтобы затереть следы его «Физики». ООП – последнее пристанище его мрачной тени. Встречаясь с ним здесь — в ядре самых передовых технологий — хочется взять античный стилус стимулус (так в Риме называли палку погонщика скота) и загнать злобного грека обратно в его каменные склеп, как это давно уже сделали все остальные.

Людвиг Витгенштейн (его афоризм вынесен в заголовок) интересен тем, что, будучи доктором философии, не прочитал и двух страниц из Аристотеля: это его – Витгенштейна — слова. Неудивительно, что неопозитивизм – единственная «работающая» философская система, по сути – единственная на сегодня корректная философия: в подтверждение могу упомянуть, например, неопозитивиста Карла Поппера, который разработал современную методологию научного познания.

Функциональное программирование стало интуитивной реакцией миллионов разработчиков на туманность ООП – его полным отрицанием. Сам я не вижу причины в том, чтобы вовсе отказываться от иерархических принципов: в них много удобного. Известные языки не предлагают готовых средств для строительства иерархии операций, и мне сейчас сложно представить, как должен выглядеть ее синтаксис и звучать ключевые слова. Перенести фокус с объекта на операцию можно и наличными средствами: любой известный ООП-язык с этим справится.

Для начала разделим бизнес-логику на два пространства: данные и операции. Пространство данных не представляет собой ничего особенного – это классы данных, построенные в обычную иерархию – живущие только в оперативной памяти (POJO) или с возможностью сохранения состояния (сущности .NET, модели YII). Классы данных могут располагать собственными методами, как того требует фреймворк: принципиально лишь, чтобы эти методы не имели отношения к бизнес-логике.

Public Class Account {
	Public string accountBankName;
	Public string accountMfo;
	Public string accountNumber;
}

Public Class Company {
	Public string companyTitle;
	Public string companyPhone;
	Public Account companyAccount;
}

Public Class Department {
	Public string departmentTitle;
	Public Company departmentCompany;
}

Public Class Person {
	Public string personName;
	Public date personBirthDate;
}

Public Class Employee inherits Person {
	Public Department employeeDepartment;
	Public double employeeSalary;
	Public Account employeeAccount;
}

Есть компания (Company) с несколькими подразделениями (Department) и сотрудниками (Employee), унаследованными от класса людей (Person). У компании есть счет (Account), с которого сотрудникам перечисляется зарплата. Соответственно, счет (Account) для получения зарплаты есть и у каждого сотрудника. Предположим, что наша программа должна уметь:

— принимать сотрудника на работу;
— выплачивать сотруднику зарплату;
— увольнять сотрудника с выплатой выходного пособия;

Прием на работу и увольнение сотрудника можно назвать кадровыми (Staff) операциями, а выплату выходного пособия и зарплаты – бухгалтерскими (Accounting) операциями.

Для каждой из операций понадобится:

— инициализировать данные о компании;
— инициализировать данные о сотруднике;
— распечатать некий документ.

Для приема / увольнения сотрудника нам придется:

— инициализировать данные о соответствующем подразделении фирмы;

Для двух указанных бухгалтерских операций нам также понадобится:

— инициализировать данные о банковских счетах сотрудника и компании.

Переходим к главному – собственно пространству операций. Мы реализуем его как иерархию классов, каждый из которых представляет собой нечто, что мы назовем «контекст операций» (Operation Context). Публичными методами (API) этих классов будут операции бизнес-логики, а свойства и приватные методы помогут в формировании абстракций. В соответствии с принятым ранее разделением, в нашей программе появятся классы StaffOperationContext и AccountingOperationContext, унаследованные от базового BaseOperationContext. Уложить вспомогательные члены в иерархию операций окажется проще, чем в иерархию объектов.

Public Class BaseOperationContext {
// конструкторы
	BaseOperationContext () {
		InitCompanyData();
	}
	BaseOperationContext (Employee employee) {
		InitEmployeeData(Employee employee);
		InitCompanyData();
	}
// приватные и защищенные методы
	private void InitCompanyData();
	private void InitEmployeeData(Employee employee);
	protected void PrintDocument(Document doc);
}

Public Class AccountingOperationContext inherits BaseOperationContext {
// конструкторы
	AccountingOperationContext ()  {
		super();
	}
// приватные и защищенные методы
	Private InitAccountData(Account account);
	Private BankTransfer(Account account, Double amount);
// публичные методы – API класса
	Public void PaySalary (Employee employee)  // выплата з/п
	{ 
// … некоторый кусок логики
		InitAccountData (employee.employeeAccount);
// … некоторый кусок логики
		BankTransfer (employee. employeeAccount, salaryAmount);
// … некоторый кусок логики
		PrintDocument (someSalaryPayDocument);
	}
	Public void PayRedundancy (Employee employee) // выплата выходного пособия
	{
// … некоторый кусок логики
		InitAccountData (employee. employeeAccount);
// … некоторый кусок логики
		BankTransfer (employee. employeeAccount, redundancyAmount);
// … некоторый кусок логики
		PrintDocument (someRedundancyPayDocument);
	}
}

Public Class StaffOperationContext inherits BaseOperationContext {
// конструкторы
	StaffOperationContext (Employee employee)  {
		super(employee);
	}
// приватные и защищенные методы
	Private InitDepartmentData(Department department);
// публичные методы – API класса
	Public void RecruitEmployee (Person person, Department department)  // прием сотрудника 
	{
		InitDepartmentData(department);
		Employee employee = person;
// … некоторый кусок логики
		PrintDocument (someRecruiteDocument);
	}
	Public void FireEmployee (Employee employee, Department department)  // увольнение
	{
		InitDepartmentData(department);
// … некоторый кусок логики
// инициализируем AccountingOperationContext для платы выходного пособия
		AccountingOperationContext accountingOC = new AccountingOperationContext ();
		accountingOC.PayRedundancy (employee);
// .. некоторый кусок логики
		PrintDocument (someFireDocument);
	}
}

Этим кодом мы ломаем принципы ООП: наш метод FireEmployee относится к своему классу StaffOperationContext как «является», а не «содержится»: то есть увольнение сотрудника становится частным случаем кадровой операции (наследником), а не ее элементом (членом). Компенсацией будет обретение здравого смысла. Некорректное высказывание «увольнение сотрудника является членом объекта 'сотрудник'» ��ы заменяем на корректное «увольнение сотрудника является кадровой операцией». Корректность высказываний дает надежду на построение корректной модели.

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

Можно по-разному классифицировать операции, важен сам принцип: объектно-ориентированное программирование мы заменяем на операционно-ориентированное. Еще раз повторюсь: речь идет исключительно о бизнес-логике – мире программы. Использовать классы среды и порождать их наследников ничто не мешает.

Не имея широкой айтишной эрудиции, могу предположить, что подобные мысли уже посещали многих философов и программистов. Вероятно, они были даже представлены в форме текстов – независимо и многократно. Сам я с подобными изысканиями не встречался, и мне потребовалось два года, чтобы прийти самостоятельно к этому удачному – как мне кажется – сочетанию объектно-ориентированного и функционального программирования (так представляется внешне).

Описанным способом я реализовал бизнес-логику в своем последнем на сегодняшний день проекте. В результате полного рефакторинга (проект достался мне по наследству) код сократился на 70% и стал невероятно дружественным в отношении любых — даже весьма значительных — правок. Опыт вышел удачным: предлагаю попробовать.