Friend is a concept in C++, which includes Friend function and Friend class. Functions or classes declared as friends by a class can access private members of that class. The correct use of friendly elements can improve the efficiency of the program, but it also destroys the encapsulation of classes and the hiding of data, resulting in poor maintainability of the program. Therefore, it is difficult to see the features of friend grammar except C++.
Ask questions
But Friends are not useless. Sometimes they do. For example, now we need to define a User class, which needs to be designed as an Immutable in order to avoid modifying the properties of the User object during use. So far, there is no problem, but the next problem comes - due to the large amount of user information, there are more than ten attribute designs. It is sad to set all the attributes for Immutable through the parameters of the construction method.
In general, we will think of such several plans:
Brief description of the scheme
Solution 1, using parameter objects
This is a common practice in JavaScript. When constructing User, we use parameter objects to provide all the set attributes through parameter objects, and then copy these attributes from the parameters to set them to read-only members by the construction method of User. Then the implementation might look like this:
To simplify the code, only three attributes, Id, Username and Name, are defined. The same below.
public sealed class User { public ulong Id { get; } public string Username { get; } public string Name { get; } public User(Properties props) { Id = props.Id; Username = props.Username; Name = props.Name; } public sealed class Properties { public ulong Id; public string Username; public string Name; } }
An attribute needs to be repeated three times. If the code is paid by line, this definition will be very profitable!
One-time settings
This is a set function for custom attributes, or a set Xxxx method to determine that if the value is null, it can be set, but it can not be set again (in theory, exceptions should be thrown, but here the example is simplified as inaction).
The following example demonstrates two methods of one-time setup through Username and Name
public class User { public ulong Id { get; } public string Username { get; private set; } public void SetUsername(string username) { if (Username == null) { Username = username; } } public string Name { get { return name; } set { if (name == null) { name = value; } } } private string name; public User(ulong id) { Id = id; } }
User in this method is not Immutalbe, but approximation, because its attributes can not be from "have" to "no", but can be from "nothing" to "have".
Moreover, I found that this method is more profitable than the previous one.
Builder
Builder mode is to solve the problem of initializing complex objects.
public class User { public ulong Id { get; } public string Username { get; internal set; } public string Name { get; internal set; } public User(ulong id) { Id = id; } } public class UserBuilder { private readonly User user; public UserBuilder(ulong id) { user = new User(id); } public UserBuilder SetUsername(string username) { user.Username = username; } public UserBuilder SetName(string name) { user.Name = name; } public User Build() { // Verify user attributes // Or do some post-processing of an attribute (such as calculation, formatting, etc.) ) return user; } }
To avoid external access, setters for all properties of User (except Id) are declared internal, because only in this way can User Builder call their setters.
Obviously, in the same assembly in this way, such as App Assembly, the properties of User are still not protected.
Internal classes implement "friend" features
Based on the above Builder pattern solution, it is easy to imagine that if UserBuilder is defined as an internal class (nested class) of User, it can directly access the private members of User in the form of the following
public class User { // .... public class UserBuilder { // .... } }
In fact, this is similar to C++'s friend class grammar - that is, it needs to be declared inside User, C++ is to declare friends, and C# is to define them at the same time.
// C++ Code class UserBuilder; class User { friend class UserBuilder; } class UserBuilder { // .... }
Internal class implements Builder pattern
There is no structural problem. Then using the partial class feature of C # to write the User class and UserBuilder class in two source files respectively, and then simplify the name of UserBuilder to Builder, because it is defined inside User, the semantics is very clear.
// User.cs public sealed partial class User { ulong Id { get; } public string Username { get; private set; } public string Name { get; private set; } public User(ulong id) { Id = id; } public static Builder CreateBuilder(ulong id) { return new Builder(id); } }
// User.Builder.cs partial sealed class User { public class Builder { private readonly User user; public Builder(ulong id) { user = new User(id); } public Builder SetUsername(string username) { user.Username = username; return this; } public Builder SetName(string name) { user.Name = name; return this; } public User Build() { // Verification and post-processing return user; } } }
The above code achieves the purpose of Immutable User, and the code is elegant. The source file is split by partitioning classes, and the structure of the code is clear. But there's a little flaw... Build() can be called repeatedly, and the user's properties can still be modified after the call.
A little more rigorous
Reusable Builder
If you want to make Build() callable multiple times, each call generates a new User object, while the generated User object is not affected by SetXxxx of the subsequent Builder, you can generate a copy of user when Build().
In addition, since the Id of each User object should be different, it is specified when the CreateBuilder is generated instead of when Build():
public partial class User { // .... public static Builder CreateBuilder()) { return new Builder(); } } partial class User { public class Builder { private readonly User user; public Builder() { user = new User(0); } // .... public User Build(ulong id) { var inst = new User(id); inst.Username = user.Username; inst.Name = user.Name; return inst; } } }
In fact, the user inside the Builder is used as a parameter object.
One-time Builder
One-time Builder is relatively simple and does not need to copy attributes in Build().
partial class User { public class Builder { private User user; // Here user is no longer readonly public Builder(ulong id) { user = new User(id); } // .... public User Build() { if (user == null) { throw new InvalidOperationException("Build Call only once") } // Verification and post-processing var inst = user; user = null; // Set user to null return inst; } } }
Once a Builder sets user to null after Build(), then calling all SetXxxx methods will throw a null pointer exception, and calling Build() method again will throw an InvalidOperationException exception.
Summary
In fact, this very common C # internal class implementation. But it does answer questions like "What if there are no friends in C #". Similar implementations can be implemented in Java, except that Java has no partial classes, so the code has to be written in a source file, which may be very long...