面试问题是:
如果您要看一个我严格遵循SOLID原则的.Net项目,您希望看到什么?就项目和代码结构而言?
我挣扎了一下,没有真正回答问题,然后被炸了。
我如何更好地解决这个问题?
#1 楼
S =单一责任原则所以我希望看到一个组织良好的文件夹/文件结构和对象层次结构。每个功能类/每个部分都应命名为其功能非常明显,并且应仅包含执行该任务的逻辑。
如果您看到包含成千上万行代码的大型经理类,则表明没有遵循单一职责。
O =开放/封闭原则
这基本上是一种想法,即应通过对现有功能影响最小/需要修改的新类添加新功能。
我希望看到对象继承,子类型,接口和抽象类的大量使用,以将功能设计与实际实现分离开来,从而使其他功能得以实现并实现。在不影响原始版本的情况下实现其他版本。
L = Liskov替换原理
这与将子类型视为其父类型的能力有关。如果您要实现适当的继承对象层次结构,则C#中提供了开箱即用的功能。
我希望看到将普通对象当作其基本类型的代码,并在基类/抽象类上调用方法,而不是实例化和处理子类型本身。 > I =接口隔离原理
这类似于SRP。基本上,您将较小的功能子集定义为接口,并与这些子集一起使用以使系统保持解耦状态(例如,
FileManager
可能只负责处理文件I / O,但是可以实现IFileReader
和IFileWriter
,其中包含特定的方法定义用于读取和写入文件)。D =依赖反转原理。
同样,这涉及保持系统解耦。也许您会注意使用.NET Dependency Injection库,该库可用于解决方案(例如
Unity
或Ninject
)或ServiceLocator系统(例如AutoFacServiceLocator
)。评论
我已经在C#中看到很多违反LSP的行为,每当有人认为自己的特定子类型是专用的,因此不需要实现一部分接口,而只需在该部分上抛出异常即可。这是一种常见的初级方法解决误解接口的实现和设计问题
–吉米·霍法(Jimmy Hoffa)
13年6月24日在15:48
@JimmyHoffa这是我坚持使用代码合同的主要原因之一;通过设计合同的思考过程,可以帮助人们摆脱这种不良习惯。
–安迪
13年6月24日在16:02
我不喜欢“ LSP在C#中开箱即用”,并且不将DIP等同于依赖注入实践。
– up
13年6月24日在17:18
+1,但依赖倒置<>依赖注入。它们在一起可以很好地发挥作用,但是依赖倒置不仅仅是依赖注入。参考:DIP在野外
– Marjan Venema
13年6月24日在18:42
@Andy:同样有用的是在接口上定义了单元测试,所有实现者(可以/被实例化的任何类)都针对该接口进行测试。
– Marjan Venema
13年6月24日在18:43
#2 楼
许多小类和接口到处都有依赖项注入。可能在大型项目中,您还将使用IoC框架来帮助您构造和管理所有这些小对象的生存期。请参阅https://stackoverflow.com/questions/21288/which-net-dependency-injection-frameworks-are-worth-look-into请注意,一个严格遵循SOLID原则的大型.NET项目不一定意味着每个人都可以使用良好的代码库。根据面试官的身份,他/她可能希望您表明您了解SOLID的含义和/或检查您遵循设计原则的原则。
您要成为SOLID,您需要遵循:
S ingle责任原则,因此您将有许多小类,每个小类仅做一件事
闭笔原则,在.NET中通常用依赖注入实现,这也需要下面的I和D ...
L iskov替换原理可能无法在c#中使用单线解释。幸运的是,还有其他问题可以解决,例如https://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example
接口隔离原则可同时使用与开放-封闭原则。如果从字面上遵循它,那就意味着倾向于使用大量非常小的接口,而不是少数几个“较大”的接口。
D倾向反转原理高级类不应依赖于低级类,而两者都应依赖于抽象。
评论
SRP并不意味着“只做一件事”。
–罗伯特·哈维(Robert Harvey)
18年4月3日在22:33
#3 楼
我希望在一家商店的代码库中能看到一些基本的东西,这些东西在日常工作中特别重视SOLID: ,以及“单一职责原则”鼓励小型模块化类结构,我希望看到很多文件,每个文件都包含一个小而集中的类。很多适配器和复合模式-期望使用大量的适配器模式(通过“传递”到另一个接口的功能来实现一个接口的类)将为一个目的而开发的依赖项简化插入稍有不同的地方,在该地方也需要其功能。如果接口被更新以提供一种指定使用文件名的方法,那么像用文件记录器替换控制台记录器那样简单的更新将违反LSP / ISP / DIP。相反,文件记录器类将公开其他成员,然后通过隐藏新内容,适配器将使文件记录器看起来像控制台记录器,因此只有将所有这些都捕捉到的对象才知道它们之间的区别。
类似地,当一个类需要添加与现有接口相似的依赖关系时,为了避免更改对象(OCP),通常的答案是实现Composite / Strategy模式(一个类实现依赖关系接口并使用多个其他接口的实现,逻辑量各不相同,允许类将调用传递给一个,一些或所有实现。)
许多接口和ABC-DIP必须要求存在抽象,而ISP鼓励对它们进行狭义范围的研究。因此,接口和抽象基类是规则,并且您将需要很多接口和类来覆盖代码库的共享依赖项功能。尽管严格的SOLID必须注入所有内容,但是很明显,您必须在某个地方创建,因此,如果仅通过对所述父表单执行某些操作将GUI表单仅作为一个父表单的子表单创建,我就不会担心会更新子表单直接从父代代码中获取。通常,我只是将该代码用作自己的方法,因此,如果两个具有相同形式的动作打开了窗口,则只需调用该方法。
许多项目-所有这些的重点是限制变更范围。变更包括需要重新编译(不再是相对琐碎的练习,但是在许多处理器和带宽关键的操作中仍然很重要,例如将更新部署到移动环境)。如果必须重建项目中的一个文件,则所有文件都必须重建。这意味着,如果将接口与其实现放置在相同的库中,则会遗漏要点;如果更改接口的实现,则必须重新编译所有用法,因为您还将重新编译接口定义本身,要求用法指向生成的二进制文件中的新位置。因此,保持接口与用法和实现分离,同时按通用的使用领域对其进行隔离是一种典型的最佳实践。
1994年的《设计模式》一书中确定的设计模式强调了SOLID试图创建的小规模模块化代码设计,这引起了人们的广泛关注。例如,依赖倒置原则和开放/封闭原则是该书中大多数已识别模式的核心。因此,我希望一家严格遵循SOLID原则的商店也采用“四人帮”一书中的术语,并根据其功能沿这些名称命名类,例如“ AbcFactory”,“ XyzRepository”,“ DefToXyzAdapter” “,“ A1Command”等。
通用存储库-与通常理解的ISP,DIP和SRP保持一致,存储库在SOLID设计中几乎无处不在,因为它允许消耗大量代码询问以抽象的方式处理数据类,而无需对检索/持久性机制有特定的了解,并且它将执行此操作的代码与DAO模式(例如,如果您有Invoice数据类,您还会有一个InvoiceDAO,它生成了这种类型的水合对象,对于代码库/模式中的所有数据对象/表,依此类推。)
IoC容器-我很犹豫添加这一点,因为我实际上不使用IoC框架来进行大部分依赖注入。它很快成为上帝对象的反模式,将所有东西扔进容器,摇晃它,然后通过注入工厂方法倒出所需的垂直水合依赖性。听起来不错,直到您意识到结构变得非常单一,并且带有注册信息的项目(如果“熟练”)现在必须了解解决方案中所有内容。这是很多改变的原因。如果它不流利(使用配置文件进行后期绑定注册),则程序的关键部分取决于“魔术字符串”,这完全不同于蠕虫。
评论
为什么要下票?
– KeithS
2013年6月26日15:25
我认为这是一个很好的答案。您没有列出许多与这些术语有关的博客文章,而是列出了示例和解释,以显示其用法和价值。
–歌声
13年6月29日在16:14
#4 楼
乔恩·斯凯特(Jon Skeet)讨论SOLID中的“ O”是“无助且理解不清”的,使他们分散注意力,并让他们谈论阿利斯泰尔·考克本(Alistair Cockburn)的“受保护的变体”和乔什·布洛赫(Josh Bloch)的“继承设计或禁止继承”。 br /> Skeet文章的简短摘要(尽管我不建议您在不阅读原始博客文章的情况下就删除他的名字!):大多数人都不知道什么是“开放”即使他们认为这样做,“封闭”也是指“封闭”。
常见的解释包括:
应该始终通过实现继承来扩展模块,或者
原始模块的源代码永远都不能更改。
模块应该具有其客户端可以依赖的定义明确的接口(不一定从技术上来说是“接口”),但是
应该可以扩展w
但是“ open”和“ closed”两个词混淆了这个问题,即使它们确实是一个很好的缩写。 >
OP问:“我如何更好地处理这个问题?”作为进行面试的高级工程师,我会比一个能从项目符号要点中脱颖而出的候选人更感兴趣,他能够聪明地谈论不同代码设计风格的利弊。
另一个很好的答案是:“嗯,这取决于他们对它的理解程度。如果他们所知道的只是SOLID流行语,我希望滥用继承,过度使用依赖注入框架,一百万个小型接口都不是。反映了用于与产品管理进行交流的领域词汇。...“
#5 楼
可能有很多方法可以在不同的时间内回答。但是,我认为这更像是“您知道SOLID意味着什么吗?”。因此,回答这个问题可能只是归结为要点并按照项目进行解释。因此,您希望看到以下内容:
类具有单一职责(例如,针对客户的数据访问类将仅从客户数据库中获取客户数据)。
类很容易扩展而不会影响现有行为。我不必修改属性或其他方法即可添加其他功能。
派生类可以替代基类,并且使用这些基类的函数不必将基类解包装为更特定的类型为了处理它们。
接口很小,易于理解。如果类使用接口,则不需要依赖多种方法来完成任务。
代码已足够抽象,因此高级实现并不具体依赖于特定的低级实现。我应该能够在不影响高级代码的情况下切换低级实现。例如,我可以将SQL数据访问层切换为基于Web服务的层,而不会影响其余的应用程序。
#6 楼
尽管我认为这是一个艰苦的面试问题,但这是一个很好的问题。我给出的简短观察或答案是,通常您会看到仅包含接口的文件,并且通常约定是:从大写字母I开始除此之外,我要提到的是文件中没有重复的代码(尤其是在模块,应用程序或库中),并且代码将在模块,应用程序或库之间的特定边界之间谨慎共享。
Robert Martin在使用Booch方法设计面向对象的C ++应用程序的C ++领域(请参阅有关内聚性,封闭性和可重用性的部分)以及“干净代码”中讨论了这个主题。
评论
.NET编码器IME通常遵循“每个文件1类”规则,并且还镜像文件夹/命名空间结构; Visual Studio IDE鼓励两种做法,并且ReSharper等各种插件都可以实施这些做法。因此,我希望看到一个反映类/接口结构的项目/文件结构。
– KeithS
2013年6月25日23:48
评论
我想知道SOLID的维基页面上有哪些不清楚的地方可扩展的抽象构建块。
通过遵循面向对象设计的SOLID原则,您的类自然会趋于小巧,结构合理且易于测试。资料来源:docs.asp.net/en/latest/fundamentals / ...