Once in a working conversation, one of my fellow programmers noticed that all sorts of principles and patterns of software design are good to use when doing test tasks, but in real, combat projects, they are usually not applicable. Why is that? There are two main reasons:
Principles and patterns take too long to implement.
The code becomes cumbersome and difficult to understand.
In a series of articles "In Practice" I will try to dispel these preconceptions by demonstrating cases that implement design principles in practical tasks in such a way that this code is not too complicated and takes a reasonable amount of time to write. Here is the first article in this series.
The starting point
We are working on a project in Yii2 , in which we use ActiveRecord
. The client code loads a set of data using the ActiveRecord::find()
.
class ClientClass
{
// ...
public function buildQuery(): ActiveQueryImplementation
{
$query = ActiveRecordModel::find(); // ActiveQuery
$query->active()->unfinished(); // , ActiveQuery
return $query; // ActiveQuery , $query->all();
}
// ...
}
This code applies a ActiveQueryInterface
fixed set of conditions to the instance that implements and returns the configured instance for later use.
What if you need to add new conditions to the request?
, , .
$query->active()->unfinished()->newConditionA()->newConditionB();
! , , .
, ? ?
. , , , , . ? ...
-
, - SOLID :
.
, .
?
-, , , .
.
class ClientClass
{
/**
* @var ActiveQueryFilter[]
*/
public $filters = []; //
// ...
public function buildQuery(): ActiveQueryImplementation
{
$query = ActiveRecordModel::find();
$query->active()->unfinished();
$this->applyFilters($query); //
return $query;
}
private function applyFilters(ActiveQueryImplementation &$query): void
{
foreach ($this->filters as $filter) {
$filter->applyTo($query);
}
}
// ...
}
ActiveQueryFitler
applyTo()
, .
interface ActiveQueryFilter
{
public function applyTo(ActiveQuery $query): void;
}
ActiveQueryFilter
.
class NewConditionsAAndBFilter implements ActiveQueryFilter
{
public function applyTo(ActiveQuery $query): void
{
$query->newCondtionA()->newConditionB();
}
}
We have solved our problem by completing the original method with just one call (minimal intervention in the original code).
The default behavior of the original method has not changed.
The new method called from the original method
applyFilters()
does not implement its own logic - all logic is delegated to the filter classes. Thus, we have left the behavior of the entire original class unchanged.