2
\$\begingroup\$

I had an exercise as below for an interview. I've added my implementation at the end. How could I improve it?

Exercise description:

Develop a simple vector based drawing package.

Your application should support the following 5 drawing primitives (we will call them widget)

1) rectangle 2) square 3) ellipse 4) circle 5) textbox

The application should allow a user to add a new widget to the drawing, stating the location and size / shape of the widget. The location can be a standard x/y coordinate on an imaginary page. The size/shape depends on the widget, as follows:

  • rectangle - width and height
  • square - width
  • ellipse - horizontal and vertical diameter
  • circle - diameter
  • textbox - bounding rectangle (i.e, the rectangle which surronds the textbox; the text will be centred within this rectangle)

Notes:

  • you don't need to actually render the widgets in any manner - we're just simulating a drawing package at this stage
  • your application can add a hard coded set of widgets to the drawing at the bootstrap, no real user input. Please add one of each widget to the drawing.

My implementation:

bootstrap.php

/*
* rectangle x=10, y=10, width=30, height=40
*/
$renderEngine = new CliRenderer();
$renderEngine->render("
-------------------------------\n
Current drawing\n
------------------------------\n
");
$rectangle = new WidgetFactory(new Widget(10,10), new Rectangle(30, 40), $renderEngine);
$rectangle->draw();
/*
* square x=15, y=30, size=35
 */
$square = new WidgetFactory(new Widget(15, 30), new Square(35), $renderEngine);
$square->draw();
/*
* ellipse x=100, y=150, horizontal diameter=300, vertical diameter=200
*/
$ellipse = new WidgetFactory(new Widget(100, 150), new Elipse(300, 200), $renderEngine);
$ellipse->draw();
/**
* circle x=1, y=1, size=300
*/
$circle = new WidgetFactory(new Widget(1,1), new Square(300), $renderEngine);
$circle->draw();
/*
* textbox x=5, y=5, width=200, height=100, text="sample text"
*/
$textBox = new WidgetFactory(new Widget(5,5), new TextBox(200, 100, "sample text"), $renderEngine);
$textBox->draw();
$renderEngine->render("------------------------------");

RenderEngineInterface.php

<?php
interface RenderEngineInterface
{
 public function render($str);
} 

CliRenderer.php

<?php
class CliRenderer implements RenderEngineInterface
{
 public function render($str)
 {
 echo $str."\n";
 }
}

WidgetFactory.php

<?php
class WidgetFactory
{
 private $widget;
 private $shape;
 private $renderEngine;
 function __construct(WidgetInterface $widget,ShapeInterface $shape,RenderEngineInterface $renderEngine)
 {
 $this->widget = $widget;
 $this->shape = $shape;
 $this->renderEngine = $renderEngine;
 }
 public function draw()
 {
 return $this->renderEngine->render($this->shape->output($this->widget));
 }
}

WidgetInterface.php

<?php
interface WidgetInterface
{
 public function getX();
 public function getY();
}

Widget.php

class Widget implements WidgetInterface
{
 private $x;
 private $y;
 function __construct($x, $y)
 {
 $this->x = $x;
 $this->y = $y;
 }
 /**
 * @return mixed
 */
 public function getX()
 {
 return $this->x;
 }
 /**
 * @param mixed $x
 */
 public function setX($x)
 {
 $this->x = $x;
 }
 /**
 * @return mixed
 */
 public function getY()
 {
 return $this->y;
 }
 /**
 * @param mixed $y
 */
 public function setY($y)
 {
 $this->y = $y;
 }
}

ShapeInterface.php

<?php
interface ShapeInterface
{
 public function output(WidgetInterface $widget);
} 

Circle.php

<?php
class Circle implements ShapeInterface
{
 private $size;
 function __construct($size)
 {
 $this->size = $size;
 }
 /**
 * @return mixed
 */
 public function getSize()
 {
 return $this->size;
 }
 /**
 * @param mixed $size
 */
 public function setSize($size)
 {
 $this->size = $size;
 }
 public function output(WidgetInterface $widget)
 {
 return "circle (".$widget->getX().",".$widget->getY().") size=".$this->size;
 }
} 

Elipse.php

<?php
class Elipse implements ShapeInterface
{
 private $diameterH;
 private $diameterV;
 function __construct($diameterH, $diameterV)
 {
 $this->diameterH = $diameterH;
 $this->diameterV = $diameterV;
 }
 public function output(WidgetInterface $widget)
 {
 return "ellipse (".$widget->getX().",".$widget->getY().") horizontal diameter=".$this->diameterH.", vertical diameter=".$this->diameterV;
 }
} 

Rectangle.php

<?php
class Rectangle implements ShapeInterface
{
 private $width;
 private $height;
 function __construct($width,$height)
 {
 $this->height = $height;
 $this->width = $width;
 }
 /**
 * @return mixed
 */
 public function getHeight()
 {
 return $this->height;
 }
 /**
 * @param mixed $height
 */
 public function setHeight($height)
 {
 $this->height = $height;
 }
 /**
 * @return mixed
 */
 public function getWidth()
 {
 return $this->width;
 }
 /**
 * @param mixed $width
 */
 public function setWidth($width)
 {
 $this->width = $width;
 }
 public function output(WidgetInterface $widget)
 {
 return "rectangle (".$widget->getX().",".$widget->getY().") width=".
 $this->width." height=".$this->height;
 }
}

Square.php

<?php
class Square implements ShapeInterface
{
 private $size;
 function __construct($size)
 {
 $this->size = $size;
 }
 /**
 * @return mixed
 */
 public function getSize()
 {
 return $this->size;
 }
 /**
 * @param mixed $size
 */
 public function setSize($size)
 {
 $this->size = $size;
 }
 public function output(WidgetInterface $widget)
 {
 return "square (".$widget->getX().",".$widget->getY().") size=".$this->size;
 }
} 

TextBox.php

<?php
class TextBox implements ShapeInterface
{
 private $width;
 private $height;
 private $text;
 function __construct($width,$height, $text)
 {
 $this->height = $height;
 $this->width = $width;
 $this->text = $text;
 }
 /**
 * @return mixed
 */
 public function getHeight()
 {
 return $this->height;
 }
 /**
 * @param mixed $height
 */
 public function setHeight($height)
 {
 $this->height = $height;
 }
 /**
 * @return mixed
 */
 public function getWidth()
 {
 return $this->width;
 }
 /**
 * @return mixed
 */
 public function getText()
 {
 return $this->text;
 }
 /**
 * @param mixed $text
 */
 public function setText($text)
 {
 $this->text = $text;
 }
 /**
 * @param mixed $width
 */
 public function setWidth($width)
 {
 $this->width = $width;
 }
 public function output(WidgetInterface $widget)
 {
 return "textbox (".$widget->getX().",".$widget->getY().") width=".
 $this->width." height=".$this->height;
 }
} 
Quill
12k5 gold badges41 silver badges93 bronze badges
asked Mar 17, 2016 at 21:23
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You have written lots of code. I would have preferred a lighter API. Something like this:

<?php
class Canvas {
 private $commands = [];
 public function moveTo($x, $y) {
 return $this->add("M $x $y");
 }
 public function square($width) {
 return $this->rectangle($width, $width);
 }
 public function rectangle($width, $height) {
 return $this->add("h $width v $height h -$width v -$height");
 }
 public function ellipse($hradius, $vradius) {
 return $this->add("TODO: some SVG commands for an ellipse($hradius, $vradius)");
 }
 public function circle($radius) {
 return $this->ellipse($radius, $radius);
 }
 public function textBox($width, $height, $text) {
 $this->rectangle($width, $height);
 return $this->add("\"$text\" tj"); // OK, this is a PDF operator, not SVG
 }
 public function draw() {
 return implode("\n", $this->commands);
 }
 private function add($command) {
 $this->commands[] = $command;
 return $this;
 }
}

The point is that the actual drawing was not part of the exercise. And if you have a little luck you have to draw it to a PDF or PostScript file or an SVG file, in which case you don't need a class per graphic primitive since you can generate the rendering code directly.

Using this canvas class is easy:

print((new Canvas())
 ->moveTo(10, 10)->rectangle(60, 30)
 ->moveTo(10, 50)->square(40)
 ->moveTo(10, 90)->ellipse(10, 30)
 ->moveTo(10, 90)->circle(20)
 ->moveTo(10, 130)->textBox(70, 10, "Hello, world")
 ->draw());

As long as the users of this canvas class don't need to manipulate the graphic primitives, there is no need to make the API more complicated than this.

I have implemented exactly this idea in a practical project, and it works great there.

answered Nov 5, 2017 at 23:18
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.