面向对象编程:如何抽取通用方法?Trait是否适用?
问题描述
我要给不同页面(webpageA、webpageB)开发WebScraper,提取标题和预览图,由于两个页面的标题标签规则不同,需要两套解析逻辑。我的目标是通过这个实践理解继承原则。
目前已实现的逻辑:控制器接收带URL的请求,判断URL所属页面后动态创建对应WebpageService[A/B]实例,调用startScraping()执行爬取。两个服务均实现了包含getTitle()和getImage()签名的IPageService接口。
现有WebpageAService代码如下:
class WebpageAService implements IPageService { private string $url; private array $data; private array $errorLog = []; private array $message = []; private $keyMap = [ 'title' => 'mappedTitleKey', // 其他映射键... ]; public function __construct($url) { $this->url = $url; } public function startScraping() { $this->data = getTheDataFromApi(); return [ 'title' => $this->getTitle(), 'image' => $this->getImage() ]; } public function getTitle(): string { $key = 'title'; if (! $this->keyExists($key)) { return "Please enter a $key"; } return $this->getValue($key); } public function getImage(): string { /* 与getTitle()逻辑类似的实现 */ } private function keyExists(string $key): bool { if (! isset($this->data[$this->keyMap[$key]])) { $this->errorLog[$key] = "Error: $key with the mapped key: {$this->keyMap[$key]} not found on {$this->url}"; return false; } return true; } private function getValue(string $key): string { return strlen($this->data[$this->keyMap[$key]]) > 0 ? $this->data[$this->keyMap[$key]] : $this->message['length']; } }
我的问题:能不能把keyExists()和getValue()这两个私有方法抽出来,避免在每个webpage[A/B]类中重复编写?如果可以,用Trait是否合理?希望得到相关建议。
解决方案与建议
1. 完全可以抽取重复逻辑,Trait是合理选择之一
keyExists()和getValue()属于通用的数据校验与取值逻辑,仅依赖类中的$data、$keyMap、$url、$errorLog、$message属性,和具体页面的解析规则无关,用Trait复用完全可行。
2. Trait实现示例
trait PageDataProcessor { protected function keyExists(string $key): bool { if (! isset($this->data[$this->keyMap[$key]])) { $this->errorLog[$key] = "Error: $key with the mapped key: {$this->keyMap[$key]} not found on {$this->url}"; return false; } return true; } protected function getValue(string $key): string { return strlen($this->data[$this->keyMap[$key]]) > 0 ? $this->data[$this->keyMap[$key]] : $this->message['length']; } }
注意:将方法访问修饰符从private改为protected,确保Trait方法能被使用它的类访问,同时不暴露给外部。
3. 在WebpageService类中使用Trait
class WebpageAService implements IPageService { use PageDataProcessor; // 引入Trait private string $url; private array $data; private array $errorLog = []; private array $message = []; private $keyMap = [ 'title' => 'mappedTitleKey', // ... ]; // 原有代码保持不变... } class WebpageBService implements IPageService { use PageDataProcessor; // 同样引入Trait private string $url; private array $data; private array $errorLog = []; private array $message = []; private $keyMap = [ 'title' => 'differentTitleKey', // 与A页面不同的映射规则 // ... ]; // 实现getTitle()、getImage()等方法,逻辑与A一致但依赖自身keyMap... }
4. 替代方案:抽象基类(更贴合继承原则学习目标)
如果所有WebpageService除了keyMap和个别细节外,大部分逻辑(比如startScraping()流程)都相似,用抽象基类实现复用更符合继承原则的设计思路:
abstract class BasePageService implements IPageService { protected string $url; protected array $data; protected array $errorLog = []; protected array $message = []; protected abstract array $keyMap; // 强制子类定义自身的映射规则 public function __construct($url) { $this->url = $url; } public function startScraping() { $this->data = $this->fetchData(); return [ 'title' => $this->getTitle(), 'image' => $this->getImage() ]; } public function getTitle(): string { $key = 'title'; if (! $this->keyExists($key)) { return "Please enter a $key"; } return $this->getValue($key); } abstract public function getImage(): string; // 强制子类实现图片提取逻辑 protected function keyExists(string $key): bool { if (! isset($this->data[$this->keyMap[$key]])) { $this->errorLog[$key] = "Error: $key with the mapped key: {$this->keyMap[$key]} not found on {$this->url}"; return false; } return true; } protected function getValue(string $key): string { return strlen($this->data[$this->keyMap[$key]]) > 0 ? $this->data[$this->keyMap[$key]] : $this->message['length']; } protected abstract function fetchData(): array; // 强制子类实现数据获取逻辑 }
子类继承示例:
class WebpageAService extends BasePageService { protected array $keyMap = [ 'title' => 'mappedTitleKey', 'image' => 'mappedImageKey' ]; public function getImage(): string { $key = 'image'; if (! $this->keyExists($key)) { return "Please enter a $key"; } return $this->getValue($key); } protected function fetchData(): array { return getTheDataFromApi(); // A页面专属的数据获取逻辑 } }
5. 两种方案选择建议
- 若仅需复用
keyExists()和getValue()这两个孤立方法,Trait更轻便,不强制类的继承结构,灵活性更高。 - 若多个WebpageService存在大量通用逻辑(如
startScraping()流程、属性声明),抽象基类更合适,能统一代码结构,也更贴合你学习继承原则的目标。
内容的提问来源于stack exchange,提问作者Max Pattern
相关产品推荐
相关产品推荐

