class SomeObject {
protected $foo,
$bar;
protected $context;
public function __construct($context) {
$this->context = $context;
}
public function setFoo($val) {
if ($this->context == 'public') {
throw new \Exception('It is impossible to modify foo property in public context!');
}
$this->foo = $val;
}
public function setBar($val) {
if ($this->context == 'api') {
throw new \Exception('It is impossible to modify bar property in API context!');
}
$this->bar = $val;
}
}
从这段“代码”中可以看出 - 对象根据上下文值限制setter。这段代码真的很难维护。我们如何重写它以使其美观且易于维护?
我的想法是:
$context
个实现接口的对象
isAllowed($object, $propertyName)
。 $context
成为对象后,我们必须考虑如何在$context
对象中存储“限制”,同时考虑到有许多与SomeObject
类似的不同对象。 / LI>
$this->context->isAllowed($this, 'foo')
- 它看起来不太好。那么,我们可能想在SomeObject上添加一些“代理”吗? $context
传递给构造函数对我来说似乎也很难看。您对此有何看法?
答案 0 :(得分:1)
只有两个一般性意见:
你可能想要将你的类分成两部分:一个不可变的基类和一个可变的扩展:
class Foo {
protected $bar, $baz;
}
class MutableFoo extends Foo {
public function setBar($bar) {
$this->bar = $bar;
}
..
}
当在对象实例化时定义上下文并且不会发生变化时,这很容易解决问题。您可以简单地实例化该类的可变或不可变版本,而不是使用确定可变性的不同上下文进行实例化。
如果您仍需要更多的运行时检查,可能只是简单地使用断言是简化代码的最佳方法:
public function setBar($bar) {
$this->assertCanSet('bar');
$this->bar = $bar;
}
protected function assertCanSet($property) {
if (!/* can set $property */) {
throw new Exception("Cannot set property $property");
}
}
答案 1 :(得分:0)
也许在构造上,填写一系列受限制的方法。 所以,例如:
class SomeObject {
protected $foo,
$bar;
protected $context;
protected $restrictedMethods;
public function __construct($context) {
$this->context = $context;
if($this->context == 'public') {
$this->restrictedMethods['setFoo'] = true;
} else if ($this->context == 'api') {
$this->restrictedMethods['setBar'] = true;
}
}
public function setFoo($val) {
if ($this->isRestricted('setFoo')) {
throw new \Exception('It is impossible to modify foo property in '.$this->context.' context!');
}
$this->foo = $val;
}
public function setBar($val) {
if ($this->isRestricted('setFoo')) {
throw new \Exception('It is impossible to modify bar property in '.$this->context.' context!');
}
$this->bar = $val;
}
protected function isRestricted($methodName) {
return array_key_exists($methodName, $this->restrictedMethods);
}
}
答案 2 :(得分:0)
如果您正在尝试编写好的OOP,那么SOLID原则中的“Interface Segregation”可能对您有用。
interface IBase
{
public function doMethod1();
public function doMethod2();
public function doMethod3();
}
interface IFoo extends IBase
{
public function setFoo($val);
}
interface IBar extends IBase
{
public function setBar($val);
}
function doWork(IBase $obj, $val)
{
$obj->doMethod1();
$obj->doMethod2();
$obj->doMethod3();
if ($obj instanceof IFoo) {
$obj->setFoo($val);
}
if ($obj instanceof IBar) {
$obj->setBar($val);
}
}
我怀疑这个例子正是你所需要的,但我会用它来解释基本的想法。
一个班级应该只有“单一责任”。但是,责任所包含的内容可能会有所不同,因此通常最好尽可能将课程的功能限制在一个关注区域。
如果你想遵循“Liskov替换”,那么仅仅因为“上下文”无关紧要而在你的函数中抛出这样的异常就违反了这个原则。
输入“界面隔离”:
通过实现接口,您(在某种程度上)向调用者保证已实现的方法,这些方法将起作用。通过排除它们,您告诉调用者这些方法不存在。
在示例中,doWork
函数需要IBase
的实例,并安全地调用该接口的方法。之后,它会对对象进行内省,以确定是否有其他“适用”方法可用。
接口隔离背后的目标是限制类强制实现的不需要的功能的数量,因此对于您而言,如果上下文为public
,则不需要setFoo
方法。
答案 3 :(得分:0)
一个干净的解决方案是拥有一个ObjectFactory
类,它基于$context
参数创建不同的对象,并且具有两个独立的类(具有公共基类),允许写入适当的属性
请在下面找到您的架构的可能实现:
/**
* Base class that allows subclasses to define which properties are
* writable via setters. Subclasses must not add public setters,
* otherwise the mechanism within this class will not work; subclasses
* can add protected setters, though
*/
class PropertyRestricter {
// only properties listed here are writable
// to be initialised by subclasses
protected $writableProperties;
public function __construct() {
// default implementation allows no writable properties
$this->writableProperties = array();
}
public function __call($method, $arguments) {
$matches = false;
// check if a setter was called, extract the property name
// there needs to be at least one argument to the setter
if(count($arguments) && preg_match('/^set([A-Z][a-zA-Z0-9_]+)$/',$matches)) {
$propName = $matches[1];
$propName[0] = strtolower($propName[0]);
// update the property with the given value
// or throw an exception if the property is not writable
if(is_array($this->writableProperties) && in_array($propName, $this->writableProperties)) {
$this->{$propName} = $arguments[0];
} else {
throw new Exception(get_class() . ": $propName is not writable");
}
} else {
// not a setter, nor a public method
// maybe display a user error
}
}
}
/**
* Common properties for the actual classes
*/
class BaseObject extends PropertyRestricter {
protected $foo, $bar;
}
class PublicObject extends BaseObject {
public function __construct() {
parent::__construct();
$this->writableProperties = array('foo');
}
}
class APIObject extends BaseObject {
public function __construct() {
parent::__construct();
$this->writableProperties = array('bar');
}
}
class ObjectFactory {
public function createObject($context) {
switch($context) {
case 'public': return new PublicObject();
case 'api': return new APIObject();
default: return null;
}
}
}
对象的根是PropertyRestricter
类,它允许子类定义哪些属性是可写的。它使用魔术方法__call()
,以便能够拦截setter调用并验证写入属性的尝试。但请注意,仅当子类不为其属性添加公共设置器时,此方法才有效。
下一级是BaseObject
类,它只定义了两个属性,以减少代码冗余。
最后一个级别包含由ObjectFactory
实例化的两个类:PublicObject
,' APIObject . These classes simply initialise the
writableProperties array, as the rest of the work is done by the
PropertyRestricter`类。
这也是一个可扩展的解决方案,因为它允许根据需要添加任意数量的属性和子类,每个子类定义其属性编写规则。
__call()
方法中的属性更新也可以自定义,我通过直接设置属性以最简单的方式实现。实际的setter可以在子类中使用,__call()
可以更新以调用setter,并提到需要保护setter以使机制工作。