迪米特法则-类与类之间关系简单化
一、概念
一个类尽量不与其他类联系,只跟特定类联系,这个法则重要的是这些特定类,特定类即我们常说的“朋友”,我们通过这些“朋友”来实现类之间关系的简化。
迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge
Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。英文简写为:
LOD。
二、狭义的迪米特法则
类A和类B之间要如果要遵循迪米特法则,那么类A调用类B的方法的时候,需要一个类C(朋友)帮忙做转发。
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者(朋友)转发这个调用。
三、哪些是“朋友”
1)类A本身
2)以参数形式传入类A方法的对象
3) 类A内部声明和创建的对象B
以上所说的这三类都可以做为转发器,帮助两个类之间做方法的转发
1)当前对象本身(this)
2)以参量形式传入到当前对象方法中的对象
3)当前对象的实例变量直接引用的对象
4)当前对象所创建的对象
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”;否则就是“陌生人”。 假设当前为类A
四、实战演武
概述:有一个聊天室程序,有两个类User和ChatClient;
User类收发信息的方法,发信息调用的是ChatClient类的方法;
ChatClient类保存User类对象集合(登录聊天室的用户们),提供发信息的方法。
// 聊天客户端类
public class ChatClient
{
private List<User> users;
public ChatClient()
{
users = new List<User>();
}
public void AddUser(User user)
{
users.Add(user);
}
//发送信息的方法,User类调用这个方法发送信息
public void SendMessage(string message, User sender)
{
foreach (User user in users)
{
if (user != sender)
{
user.ReceiveMessage(message);
}
}
}
}
// 用户类
public class User
{
private string name;
public User(string name)
{
this.name = name;
}
//用户类调用ChatClient类的SendMessage方法进行发送信息
public void SendMessage(string message, ChatClient chatClient)
{
chatClient.SendMessage(message, this);
}
public void ReceiveMessage(string message)
{
Console.WriteLine("Received message: " + message);
}
}
//客户端调用
static void Main(string[] args)
{
ChatClient chatClient = new ChatClient();
User user1 = new User("myx");
User user2 = new User("pxy");
chatClient.AddUser(user1);
chatClient.AddUser(user2);
user1.SendMessage("Hello", chatClient);
user2.SendMessage("Hi", chatClient);
Console.ReadKey();
}
在上面的设计中,User类和ChatClient类之间的SendMessage方法是不满足“迪米特法则”的,同时如果ChatClient类的SendMessage方法又有可能是变化的,两个类之间,如果其中一个类的方法有可能变化,而我们却不使用迪米特法则”,这样会有什么问题呢?
假如有一天,我们不再使用ChatClient类发送消息,改用消息中心(MessageCenter)来进行发送消息:
public class MessageCenter
{
private List<User> users= new List<User>();
public void AddUser(User user)
{
users.Add(user);
}
public void SendMessage(string message, User sender)
{
//消息中心发送信息
}
}
这个时候,因为之前没有满足“迪米特法则”,那么我们修改的地方很多;
1.需要修改 User 类的 SendMessage 方法,修改为
public void SendMessage(string message, MessageCenter messageCenter)
{
messageCenter.SendMessage(message, this);
}
2.需要修改 ChatClient 类的 SendMessage 方法,用不到了,可以注释或者删掉
public void SendMessage(string message, User sender)
{
messageCenter.SendMessage(message, sender);
}
从这里看,不用“迪米特法则”也就只需要改两个地方(当然第二点可改可不改),
好像也没什么!
但是在具体项目中,
系统代码中所有出现ChatClient 类的我们都要改成MessageCenter类,
这样想一想是不是就感觉需要改很多东西了,
大项目中好几个程序员分管不同的模块,你可能还要通知他们,
你们代码中用到ChatClient 类的,现在都要改成MessageCenter类,
是不是感觉非常麻烦!
那么,假如我们User类和ChatClient 类一开始就遵循“迪米特法则”的话,
代码改写如下:
// 用户类
public class User
{
private string name;
public User(string name)
{
this.name = name;
}
//1.由Frend类负责与外界(ChatClient)交流
public void SendMessage(string message,Frend frend)
{
frend.SendMessage(message,this);
}
public void ReceiveMessage(string message)
{
Console.WriteLine("Received message: " + message);
}
}
//1.创建一个User类的朋友类,用于帮助User类SendMessage方法的转发
public class Frend
{
ChatClient chatClient;
public Frend(ChatClient chatClient)
{
this.chatClient = chatClient;
}
public void SendMessage(string message,User user)
{
//1.Frend内部调用的是ChatClient类的SendMessage方法,即由Frend代替User与ChatClient类交流。
chatClient.SendMessage(message, user);
}
public void AddUser(User user)
{
chatClient.AddUser(user);
}
}
// 聊天客户端类
public class ChatClient
{
private List<User> users;
public ChatClient()
{
users = new List<User>();
}
public void AddUser(User user)
{
users.Add(user);
}
//发送信息的方法,User类调用这个方法发送信息
public void SendMessage(string message, User sender)
{
foreach (User user in users)
{
if (user != sender)
{
user.ReceiveMessage(message);
}
}
}
}
客户端调用
static void Main(string[] args)
{
Frend frend = new Frend(new ChatClient());
User user1 = new User("myx");
User user2 = new User("pxy");
frend.AddUser(user1);
frend.AddUser(user2);
user1.SendMessage("Hello", frend);
user2.SendMessage("Hi", frend);
Console.ReadKey();
}
当代码改写完以后,这个时候假如遇到之前的情况,发送消息改用消息中心MessageCenter,此时我们只需要修改Frend类即可,改写如下:
public class Frend
{
MessageCenter messageCenter;
//ChatClient chatClient;
public Frend(MessageCenter messageCenter)
//public Frend(ChatClient chatClient)
{
this.messageCenter = messageCenter;
}
public void SendMessage(string message, User user)
{
messageCenter.SendMessage(message, user);
}
public void AddUser(User user)
{
messageCenter.AddUser(user);
}
}
可能有人会说,用了这个法则也要改Frend类,改的还挺多(从这个例子看,改的很多)
而不用这个法则的话直接新建一个类MessageCenter ,然后User类调用ChatClient替换成MessageCenter ,看起来不用法则反而改的少还轻松?
实际上,User类作为给其他各个模块类调用的类,肯定是被成百上千个类引用的,
而User类SendMessage一改动(本例中形参改了),
则系统中调用SendMessage方法的成千上百的都要一起改,
而使用法则以后,你只需要改Frend类,
因为Frend类只是用来做类关系隔离的,所以只有User类会引用Frend类,
几乎没有其他类会调用Frend类,
你是希望修改一个几乎不被引用的类,还是希望修改被成千上百被引用的类?
当前使用这个法则,在编程中一般用接口或者抽象类实现,面向接口编程,
其实这个Frend类就相当于接口或者抽象类,我们用接口或者抽象类完成Frend的作用,
则代码再次改写如下:
//1.这个相当于之前的Frend
public abstract class ChatBase
{
public List<User> users = new List<User>();
public abstract void AddUser(User user);
public abstract void SendMessage(string message, User sender);
}
// 用户类
public class User
{
private string name;
public User(string name)
{
this.name = name;
}
//1.形参由Frend换成ChatBase抽象类
public void SendMessage(string message, ChatBase frend)
{
frend.SendMessage(message,this);
}
public void ReceiveMessage(string message)
{
Console.WriteLine("Received message: " + message);
}
}
ChatClient类改写
// 聊天客户端类
public class ChatClient: ChatBase
{
public override void AddUser(User user)
{
users.Add(user);
}
public override void SendMessage(string message, User sender)
{
foreach (User user in users)
{
if (user != sender)
{
user.ReceiveMessage(message);
}
}
}
}
当我们要替换成消息中心的时候,只需要MessageCenter实现ChatBase抽象类即可,代码不需要做修改了。
public class MessageCenter: ChatBase
{
public override void AddUser(User user)
{
users.Add(user);
}
public override void SendMessage(string message, User sender)
{
foreach (User user in users)
{
if (user != sender)
{
user.ReceiveMessage("消息中心:"+message);
}
}
}
}
以上就是迪米特法则,你看懂了吗?