Я добавляю некоторый код в класс php, который имеет несколько функций с повторяющимся кодом, который я бы хотел реорганизовать. Функции выглядят следующим образом:
public function similarName($arg1, $arg2, $differentObject)
{
// $arg1 and $arg2 are common to all functions
if ($firstCheck) {
// some code
if($secondCheck) {
// some code
if ($thirdCheck) {
// unique code with $differentObject
}
}
}
// return statement
}
Мне нужно добавить новую функцию, точно такую же, но уникальный код.
Вот что мне пришло в голову:
Какая правильная картина для этого?
ОБНОВИТЬ:
Ниже приведены две из этих функций с реальным кодом. Это обработчики форм Symfony2, которые добавляют загруженное изображение для объекта
public function handleToPost(FormInterface $form, Request $request, Post $post)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
if (!$post->getImage()) {
$image = new ImagePost();
$image->setPost($post);
$image->setFile($file);
$this->imageManager->saveImage($image);
} else {
$image = $post->getImage();
$image->setFile($file);
}
$this->imageManager->createImage($image);
} else {
return false;
}
return true;
}
}
return false;
}
public function handleToEvent(FormInterface $form, Request $request, Event $event)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if ($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
if (!$event->getImage()) {
$image = new ImageEvent();
$image->setEvent($event);
$image->setFile($file);
$this->imageManager->saveImage($image);
} else {
$image = $event->getImage();
$image->setFile($file);
}
$this->imageManager->createImage($image);
} else {
return false;
}
return true;
} else {
return true;
}
}
return false;
}
Хороший вопрос.
Основываясь на двух примерах, кажется, что как классы Post, так и Event должны реализовать своего рода интерфейс "ImageSource". Если другие случаи схожи, и если предположить, что событие, сообщение и другие классы можно легко изменить, на мой взгляд, код должен быть примерно таким. Позвольте называть общую функцию "handleImageSource":
public function handleImageSource(FormInterface $form, Request $request, ImageSource $imgsrc)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if ($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
$image = $imgsrc->createImageFromFile($file, $this->imageManager);
} else {
return false;
}
return true;
} else {
return true;
}
}
return false;
}
Затем каждый класс, реализующий интерфейс ImageSource, должен иметь такой метод. Например, в классе Event:
public function createImageFromFile($file, $imageManager)
{
if (!($image = $this->getImage()) ) {
$image = new ImageEvent();//|| new ImagePost() || etc...
$image->setEvent($this);//|| setPost() || etc...
$image->setFile($file);
$imageManager->saveImage($image);
} else {
$image->setFile($file);
}
$imageManager->createImage($image);
return $image;
}
В других случаях, или если вы хотите реорганизовать класс только с помощью методов "handleToXxxx", я бы создал анонимную функцию с другим кодом непосредственно перед каждым вызовом. Например, снова с классом Event:
$image_source = function($file, $imageManager) use ($Event){
if (!($image = $Event->getImage()) ) {
$image = new ImageEvent();//|| new ImagePost() || etc...
$image->setEvent($this);//|| setPost() || etc...
$image->setFile($file);
$imageManager->saveImage($image);
} else {
$image->setFile($file);
}
$imageManager->createImage($image);
return $image;
};
//Then call to the function
$theHandleObj->handleImageSource($form, $request, $image_source);
Затем в классе "$ theHandleObj":
public function handleImageSource(FormInterface $form, Request $request, callable $imgsrc)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if ($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
$image = $imgsrc($file, $this->imageManager);
} else {
return false;
}
return true;
} else {
return true;
}
}
return false;
}
Надеюсь это поможет :)
imgsrc->createImageFromFile
нуждается во $image_manager
объекте $image_manager
, так как насчёт того, как я говорил ранее об использовании наследования и фабрики? Это почти то же самое, но я мог бы вместо этого ввести объект в основной класс :) Спасибо @juanra
Трудно вам посоветовать, не зная точно, что представляют ваши данные.
Если аргументы одинаковы для всех функций, и вы выполняете некоторые операции над ними до того, uniqueCode
операции uniqueCode
не будут лучше создавать объект, в котором у вас будут ваши аргументы как параметры и ваши проверки как методы?
Другими словами, uniquecode
, по-видимому, является ядром вашей функции. Оставьте его видимым и читаемым и отредактируйте часть проверки.
Что-то вроде:
class ValidateArgs {
private $arg1;
private $arg2;
public function __construct($arg1, $arg2) {
$this->arg1 = $arg1;
$this->arg2 = $arg2;
}
public function check() {
//Check
$check = $this->arg1 && $this->arg2;
return $check;
}
}
И тогда вы бы назвали это так:
public function similarName($arg1, $arg2, $differentObject)
{
$validation = new ValidateArgs($arg1, $arg2);
if($validation->check()) {
// unique code goes here
return $result_of_unique_code;
}
}
ОБНОВИТЬ:
После просмотра вашего примера кода я считаю, что вам нужно много итераций, чтобы успешно реорганизовать его. Пойдите медленно шаг за шагом, пытаясь получить код немного чище на каждом шаге.
Вот несколько предложений:
Упростите структуру if/else. Иногда вы можете полностью исключить использование части else
. Например, вы можете проверить $request->getMethod()
и немедленно вернуться:
if ($request->getMethod() != 'POST') {
return false;
}
//Rest of the code
Проверка count($errorList)
видимому, зависит только от data['file']
. Думаю, вы могли бы создать функцию со всей этой логикой. Что-то вроде этого:
public function constrainValidations($data) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($data['file'], $imageConstraint);
if (count($errorList) == 0) {
return true;
} else {
return false;
}
}
Тогда ваш исходный код начнет выглядеть немного более чистым и читаемым:
if ($request->getMethod() != 'POST') {
return false;
}
if (!$this->constrainValidations($data)) {
return false;
}
//Unique code goes here
return true;
Продолжайте делать это шаг за шагом. Попытка отключить все операторы if. Кроме того, это позволит вам изменить операторы return
и начать выброс исключений.
Затем вы можете начать думать об объектном подходе для дополнительной удобочитаемости.
Я бы лично избегал любого решения, которое включает обратный вызов, но это вопрос вкуса.
Ответ может состоять в том, чтобы переместить "уникальный код" в другой класс объекта, если он зависит только от типа объекта.
Если это зависит от факторов, отличных от типа объекта, то действительно хороший подход - это "обратный вызов",
private function commonFunction($arg1, $arg2, $differentObject, $uniqueCallback)
{
// $arg1 and $arg2 are common to all functions
if ($firstCheck) {
// some code
if($secondCheck) {
// some code
if ($thirdCheck) {
$uniqueCallback($differentObject);
}
}
}
// return statement
}
public function similarFunction($arg1, $arg2, $differentObject)
{
$this->commonFunction($arg1, $arg2, $differentObject,
function($differentObject) {
// uniqueCode
});
}
$differentObject->uniqueCode()
, и различные подклассы differentObject должны реализовать его по-разному. Что-то вроде. Это зависит от того, должен ли «беспокойство» другого объекта включать «уникальный код». Это немного продвинуто для неконкретного примера.