Post

SOLID – Interface segregation principle

SOLID – Interface segregation principle

W klasie implementującej interfejs znaleźć muszą się implementacje wszystkich metod zawartych w tym interfejsie. W przypadku gdy w danej klasie nie potrzebujemy wszystkich metod, pojawia się problem. Przestrzeganie kolejnej z zasad programowania obiektowego SOLID pozwoli nam uniknąć takich kłopotów.

Zasada Interface segregation principle mówi nam, że klasa nigdy nie powinna być zmuszana do implementacji metod, których nie używa. W celu osiągnięcia takiego stanu rzeczy należy tworzyć zwięzłe interfejsy zamiast tzw. grubych interfejsów. Powinny one zawierać metody ściśle ze sobą powiązane.

Przykład złamania

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

/**
 * Interface ShapeInterface
 */
interface ShapeInterface
{
    /**
     * @return float
     */
    public function calcArea(): float;

    /**
     * @return float
     */
    public function calcVolume(): float;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php

/**
 * Class Rectangle
 */
final class Rectangle implements ShapeInterface
{
    /**
     * @var float
     */
    private $width;

    /**
     * @var float
     */
    private $height;

    /**
     * Rectangle constructor.
     * @param float $width
     * @param float $height
     */
    public function __construct(float $width, float $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    /**
     * @return float
     */
    public function getWidth(): float
    {
        return $this->width;
    }

    /**
     * @return float
     */
    public function getHeight(): float
    {
        return $this->height;
    }

    /**
     * @return float
     */
    public function calcArea(): float
    {
        return $this->width * $this->height;
    }

    /**
     * @return float
     */
    public function calcVolume(): float
    {
        return 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php

/**
 * Class Cuboid
 */
final class Cuboid implements ShapeInterface
{
    /**
     * @var float
     */
    private $a;

    /**
     * @var float
     */
    private $b;

    /**
     * @var float
     */
    private $c;

    /**
     * Cuboid constructor.
     * @param float $a
     * @param float $b
     * @param float $c
     */
    public function __construct(float $a, float $b, float $c)
    {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }

    /**
     * @return float
     */
    public function getA(): float
    {
        return $this->a;
    }

    /**
     * @return float
     */
    public function getB(): float
    {
        return $this->b;
    }

    /**
     * @return float
     */
    public function getC(): float
    {
        return $this->c;
    }

    /**
     * @return float
     */
    public function calcArea(): float
    {
        return 2 * ($this->a * $this->b + $this->c * $this->b + $this->a * $this->c);
    }

    /**
     * @return float
     */
    public function calcVolume(): float
    {
        return $this->a * $this->b * $this->c;
    }
}

W powyższym przykładzie mamy interfejs ShapeInterface oraz dwie klasy(Rectangle, Cuboid), które go implementują. Z racji, że w figurach płaskich obliczamy jedynie pole, natomiast objętość jest przeznaczona dla figur przestrzennych to metody calcArea() oraz calcVolume() nie powinny się znaleźć w jednym interfejsie.

Poprawny przykład

1
2
3
4
5
6
7
8
9
10
11
12
<?php

/**
 * Interface AreaCalculableInterface
 */
interface AreaCalculableInterface
{
    /**
     * @return float
     */
    public function calcArea(): float;
}
1
2
3
4
5
6
7
8
9
10
11
12
<?php

/**
 * Interface VolumeCalculableInterface
 */
interface VolumeCalculableInterface
{
    /**
     * @return float
     */
    public function calcVolume(): float;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php

/**
 * Class Cuboid
 */
final class Cuboid implements VolumeCalculableInterface, AreaCalculableInterface
{
    /**
     * @var float
     */
    private $a;

    /**
     * @var float
     */
    private $b;

    /**
     * @var float
     */
    private $c;

    /**
     * Cuboid constructor.
     * @param float $a
     * @param float $b
     * @param float $c
     */
    public function __construct(float $a, float $b, float $c)
    {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }

    /**
     * @return float
     */
    public function getA(): float
    {
        return $this->a;
    }

    /**
     * @return float
     */
    public function getB(): float
    {
        return $this->b;
    }

    /**
     * @return float
     */
    public function getC(): float
    {
        return $this->c;
    }

    /**
     * @return float
     */
    public function calcArea(): float
    {
        return 2 * ($this->a * $this->b + $this->c * $this->b + $this->a * $this->c);
    }

    /**
     * @return float
     */
    public function calcVolume(): float
    {
        return $this->a * $this->b * $this->c;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php

/**
 * Class Rectangle
 */
final class Rectangle implements AreaCalculableInterface
{
    /**
     * @var float
     */
    private $width;

    /**
     * @var float
     */
    private $height;

    /**
     * Rectangle constructor.
     * @param float $width
     * @param float $height
     */
    public function __construct(float $width, float $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    /**
     * @return float
     */
    public function getWidth(): float
    {
        return $this->width;
    }

    /**
     * @return float
     */
    public function getHeight(): float
    {
        return $this->height;
    }

    /**
     * @return float
     */
    public function calcArea(): float
    {
        return $this->width * $this->height;
    }
}

W tym przykładzie interfejs został rozbity, przez co w klasie Rectangle implementujemy tylko AreaCalculableInterface i możemy usunąć zbędną metodę calcVolume(), dzięki czemu kod jest zgodny z zasadą ISP.

Unit Testing Tips - Kamil Ruczyński
This post is licensed under CC BY 4.0 by the author.