我能想到的最简单的例子来说明这个问题涉及两个记录器类。
在此示例中,LoggerA和LoggerB都实现了ILogger接口以实现多态性。
public interface ILogger
{
void SomeMethodBNeeds;
void LogB;
bool LogA;
}
public class LoggerA : ILogger
{
public void SomeMethodBNeeds()
{
// Do nothing; un-needed here.
}
public bool LogA()
{
// Perform some work.
}
public void LogB()
{
LogA();
}
}
public class LoggerB : ILogger
{
public void SomeMethodBNeeds()
{
// Perform some work.
}
public bool LogA()
{
LogB();
return true;
}
public void LogB()
{
// Perform some work.
}
}
由于ILogger是一个接口,LoggerA和LoggerB都需要实现每个方法,即使LoggerA不需要SomeMethodBNeeds
。因此SomeMethodBNeeds
为空并且在LoggerA中不执行任何操作。
这同样适用于LogA和LogB方法;它们是不同的,但在某些地方需要和使用。在这种情况下,他们都实现了相同的目标,但是有不同的方法签名,因此我只是从另一个中调用一个来解决松散的问题"。
然后有第三个拥有类来实现多态,并且需要成为调用这些方法的类。
class SomeOwningClass : SomeOwningBaseClass
{
ILogger myLogger;
#if DEBUG
myLogger = new LoggerA();
#else
myLogger = new LoggerB();
#endif
protected override void ProgramLaunched()
{
myLogger.SomeMethodBNeeds();
}
protected override void ProgramStopped()
{
}
protected override void ProgramResumed()
{
myLogger.SomeMethodBNeeds();
}
protected override void ProgramPaused()
{
}
}
显然,这应该会给任何优秀的开发人员带来许多危险信号,因为它似乎没有按预期使用接口。如果打算这样做,你就不必在接口中实现每个函数,你也不会简单地从另一个函数中调用Log
个方法之一,或者抛出未实现的异常。 / p>
接下来有人会想到使用抽象类,但LoggerA和LoggerB真的不共享代码,那么这是否构成了对Abstract类的正确使用?
答案 0 :(得分:2)
当基本实现调用派生方法时会发生多态性。因此,接口继承是不可能的,它没有实现继承。
记住多态性意味着什么的一种方法......开发人员可以在2005年编写基类,多年后开发人员可以编写一个修改其某些行为的派生类。因此原始代码变形成为一种新行为,即使它从未被触及过。随着越来越多的派生类添加基类多态的功能。
在某种程度上,多态意味着基类具有它可以依赖的(内部)接口。这与实现外部接口不同,后者不是对基类的承诺,而是对来自类外部的调用的承诺。
接口是与调用者的合同。将接口添加到类时,您承诺支持接口所暗示的。只应包括承诺的方法;像SomeMethodBNeeds
这样的方法不应包含在该合同中。仅包含调用者所需的方法,而不包括实现该接口的其他类。
在特殊情况下,可以在界面中包含并非总是实现的内容。一个很好的例子,是IEndpointBehavior接口的ApplyDispatchBehavior方法;此接口可以为客户端,服务器或两者实现。如果它只是客户端,则不需要ApplyDispatchBehavior,因此应对其进行编码以抛出NotImplementedException
(在这种情况下请参阅Microsoft's recommendation to do so)。
只需从界面定义中删除SomeMethodANeeds
和SomeMethodBNeeds
即可。这样,A类仍然可以调用SomeMethodANeeds(它知道它,因为它在它自己的类中)而B类仍然可以调用SomeMethodBNeeds,但是调用者不知道如何调用它们(并且不应该)只是界面。我还建议将SomeMethodANeeds和SomeMethodBNeeds限定为私有或受保护 - 这意味着它们不能在界面中开始,因为接口方法必须是公共的。
现在,您可能无法删除其中一种方法,因为您需要某种方法将特定于类的参数传递给方法。例如,如果您有FlatFileLogger
和SQlLogger
,则可能SqlLogger需要连接字符串,而FlatFileLogger则不需要。{1}}如果是这种情况,您就已经不正确地设计了界面。 任何调用者都可以使用接口,而不了解实现该接口的类的实现。
话虽如此,你总是可以欺骗,就像这样:
ILogger _logger = GetLogger(); //Might be any type of Logger
SqlLogger sqlLogger = _logger as SqlLogger; //Try to get class-specific interface
if (sqlLogger != null) //Logger is in fact a SqlLogger
{
sqlLogger.SetConnectionString(AppConfig.LoggerConnectionString); //Call method not in interface
}
_logger.Log("Logger started."); //Using class-agnostic interface to actually log data
^我经常看到这样的东西,但它有点破坏了拥有一个通用界面的目的。如果代码需要特定于类的逻辑,那么代码当然不会与实现接口的其他类向前兼容。为了保持关注点的分离,可能应该将特定于类的逻辑移动到工厂方法,如下所示:
ILogger GetLogger() //Factory method. Uses config to determine what sort of ILogger to return.
{
if (AppConfig.UseFlatFileLogger) return new FlatFileLogger();
if (AppConfig.UseSqlLogger )
{
SqlLogger sqlLogger = new SqlLogger();
sqlLogger.SetConnectionString(GetLoggerConnectionString()); //Call method not in interface
return sqlLogger;
}
throw new ConfigurationException("Logger type not supported.");
{
//Main program. Notice 100% of this code is class-agnostic.
ILogger _logger = GetLogger();
_logger.Log("Logger started.");
没有规则说所有类的方法都需要在接口中。您可以将该方法保留在类中,但将其保留在接口之外。如果您确实不能那样做(因为调用者也需要特定于类的方法),那么您已经不正确地设计了接口。
答案 1 :(得分:1)
您应遵循SOLID原则。具体的接口隔离原则。因此,不要强制两个类实现相同的接口,而是将该接口分开,以便A不需要实现B需要的方法,反之亦然。
更多here
public interface ILogger
{
void LogB();
bool LogA();
}
public class LoggerA : ILogger
{
public bool LogA()
{
return true;
}
public void LogB()
{
LogA();
}
}
public class LoggerB : ILogger
{
public bool LogA()
{
LogB();
return true;
}
public void LogB()
{
// Perform some work.
}
}
public interface ISomeMethod
{
void SomeMethodBNeeds();
}
public class D : ISomeMethod
{
public void SomeMethodBNeeds()
{
// Perform some work.
}
}
class C
{
ISomeMethod myLogger;
public C()
{
myLogger = new D();
}
protected void ProgramLaunched()
{
myLogger.SomeMethodBNeeds();
}
protected void ProgramStopped()
{
}
protected void ProgramResumed()
{
myLogger.SomeMethodBNeeds();
}
protected void ProgramPaused()
{
}
}
答案 2 :(得分:1)
你应该遵循SOLID原则,你还需要学习面向对象的4个支柱(在这种情况下是封装)。
A-man是对的。但是,他在这里错过了这个问题。
OP在哪里缺少有很多方法可以给猫皮肤。
你的界面应该做好一件事。特别是记录。
public interface ILogger
{
void LogA();
bool LogB();
}
LoggerB需要其他东西才能完成它的工作超出了界面的范围。
举一个抽象类汽车的例子。
public abstract class Engine {
void PressAccelerator()
}
对于抽象汽车,我们知道按下加速器会使车速表上升。
让我们实施汽车
public class PetrolEngine : Engine
{
public void PressAccelerator()
{
this.InjectFuelInto(_engine);
}
}
好。这看起来很简单。
但混合动力汽车怎么样?
public class HybridEngine : Engine
{
private IBattery _battery;
private bool HasElectricCharge()
{
return _battery.HasCharge();
}
public void PressAccelerator()
{
if(HasElectricCharge())
this.PumpElectronsInto(_electricMotor);
else
this.InjectFuelInto(_engine);
}
}
我们在这里做的是封装。我们从IEngine界面隐藏了HybridEngine的复杂性。
我的建议,在你的情况下。
让LoggerB有一个eventhandler
响应MyProgram.Started
和MyProgram.Stopped
或MyProgram.RunStateChanged
。
或....您将MyProgram
注入LoggerB
并LoggerB
查询MyProgram.RunState
。
答案 3 :(得分:1)
根据评论,我推断出以下要求:
我将如何做到这一点:
public interface ILogger
{
void Log(string message);
}
class BasicLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message); //Or use whatever output method is suitable
}
}
class StatefulLogger : ILogger, IDisposable
{
public StatefulLogger()
{
StartSession();
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
}
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
EndSession();
}
public void Dispose()
{
EndSession();
}
private void StartSession()
{
Console.WriteLine("Logger session started.");
//Do Stuff
}
private void EndSession()
{
AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit;
//Do stuff
Console.WriteLine("Logger session ended.");
}
public void Log(string message)
{
Console.WriteLine(message);
}
}
使用上面的代码,希望使用ILogger接口的程序员不必担心记录器的实现,并且记录器类将正确地建立和拆除会话而无需任何干预。这样可以保持关注点的分离和ILogger接口的完整性。