不违反SOLID原则,如何设计类结构解决子类方法名差异调用问题?
这个问题其实是典型的**依赖倒置原则(DIP)和接口隔离原则(ISP)**的踩坑场景——你在doSomething里硬调用了Singer接口没有定义的writeSong()方法,而PureSinger根本没实现这个能力,自然编译失败。要实现预期功能(对纯歌手只调用唱歌,对创作型歌手既唱又写),同时不违反SOLID,给你几个靠谱的方案:
方案1:拆分方法,基于抽象调用(简单场景首选)
既然Singer和SongWriter是两个独立的职责,那客户端就不该在同一个方法里同时依赖两个抽象的能力。我们可以把逻辑拆分开,让客户端根据对象的实际抽象类型来调用对应的方法:
// 保持原接口和实现类不变 public interface Singer{ void sing(); } public interface SongWriter{ void writeSong(); } public class PureSinger implements Singer{ @Override public void sing(){ System.out.println("纯歌手正在唱歌"); } } public class SingerSongWriter implements Singer, SongWriter{ @Override public void sing(){ System.out.println("创作型歌手正在唱歌"); } @Override public void writeSong(){ System.out.println("创作型歌手正在写歌"); } } // 客户端代码调整 void methodA(){ Singer objPureSinger = new PureSinger(); // Java 16+ 可使用联合类型:Singer & SongWriter objSingerSWer = new SingerSongWriter(); SingerSongWriter objSingerSWer = new SingerSongWriter(); doSing(objPureSinger); doSingAndWrite(objSingerSWer); } // 只处理唱歌逻辑,依赖Singer抽象 public void doSing(Singer obj){ obj.sing(); } // 处理既唱又写的逻辑,依赖同时实现两个接口的类型 public void doSingAndWrite(SingerSongWriter obj){ obj.sing(); obj.writeSong(); }
这个方案严格遵循接口隔离原则,每个方法只依赖完成职责需要的抽象,不会强迫Singer去承担它不需要的writeSong能力,同时符合依赖倒置——高层方法依赖的是抽象接口而非具体实现。
方案2:使用访问者模式,实现双重分派(复杂/可扩展场景首选)
如果不想让客户端做类型判断,或者未来可能新增更多类型(比如只会写歌不会唱的PureSongWriter),访问者模式是更优雅的选择,完全符合开闭原则——新增类型只需要扩展访问者,不需要修改现有逻辑:
// 定义访问者接口,对应不同的艺人类型 public interface ArtistVisitor { void visit(PureSinger singer); void visit(SingerSongWriter singerSongWriter); } // 修改Singer接口,添加接受访问者的方法 public interface Singer{ void sing(); void accept(ArtistVisitor visitor); } // PureSinger实现accept方法 public class PureSinger implements Singer{ @Override public void sing(){ System.out.println("纯歌手正在唱歌"); } @Override public void accept(ArtistVisitor visitor){ visitor.visit(this); } } // SingerSongWriter实现accept方法 public class SingerSongWriter implements Singer, SongWriter{ @Override public void sing(){ System.out.println("创作型歌手正在唱歌"); } @Override public void writeSong(){ System.out.println("创作型歌手正在写歌"); } @Override public void accept(ArtistVisitor visitor){ visitor.visit(this); } } // 实现具体的业务访问者 public class PerformanceVisitor implements ArtistVisitor { @Override public void visit(PureSinger singer){ singer.sing(); } @Override public void visit(SingerSongWriter singerSongWriter){ singerSongWriter.sing(); singerSongWriter.writeSong(); } } // 客户端代码 void methodA(){ Singer objPureSinger = new PureSinger(); Singer objSingerSWer = new SingerSongWriter(); ArtistVisitor visitor = new PerformanceVisitor(); objPureSinger.accept(visitor); objSingerSWer.accept(visitor); }
这里通过双重分派,让具体的实现类自己决定如何接受访问者,客户端只需要统一调用accept方法即可。新增类型时,只需要给ArtistVisitor加一个visit方法,再让新实现类实现accept,完全符合开闭原则,也不会违反其他SOLID规则。
方案3:谨慎使用默认方法(仅特定场景)
如果业务上允许“所有歌手都可以尝试写歌,但纯歌手写不了”,可以给Singer接口加默认方法,但要注意这可能违反里氏替换原则(因为PureSinger作为Singer的实现,却不支持writeSong的契约),所以只适合特定场景:
public interface Singer{ void sing(); // 默认实现,抛出异常表示不支持该能力 default void writeSong(){ throw new UnsupportedOperationException("该歌手不具备写歌能力"); } } public interface SongWriter{ void writeSong(); } public class PureSinger implements Singer{ @Override public void sing(){ System.out.println("纯歌手正在唱歌"); } } public class SingerSongWriter implements Singer, SongWriter{ @Override public void sing(){ System.out.println("创作型歌手正在唱歌"); } @Override public void writeSong(){ System.out.println("创作型歌手正在写歌"); } } // 客户端doSomething可以保留,但需要异常处理 public void doSomething(Singer obj){ obj.sing(); try{ obj.writeSong(); } catch(UnsupportedOperationException e){ // 处理纯歌手的情况,比如忽略或打印提示 System.out.println(e.getMessage()); } }
这个方案的缺点很明显——它破坏了Singer接口的单一职责,而且需要用异常处理来控制流程,不是推荐的做法,除非业务逻辑确实要求这样的“可选能力”。
内容的提问来源于stack exchange,提问作者Tando

