开发者

If security is not an issue, is an immutable interface a good pattern?

开发者 https://www.devze.com 2023-04-08 16:57 出处:网络
It is commonly suggested that immutable classes should be sealed, to enforce a promise to consumers that observed properties of the class will remain invariant.Certainly that would seem a good practic

It is commonly suggested that immutable classes should be sealed, to enforce a promise to consumers that observed properties of the class will remain invariant. Certainly that would seem a good practice for classes that would be employed in security contexts. On the other hand, there are a number of cases where it may be useful to have a number of immutable classes with common base features, and also have editable versions of such classes.

For example, a graphics program might have a DrawnText object which contains a location, font, and string, and a derivative DrawnFancyText string which adds parameters to curve text around a shape. It may be useful in some contexts to have immutable versions of those objects (e.g. for things like undo buffers), but in other contexts it may be more useful to have mutable versions.

In such a context, there are some contexts where one will need a readable DrawnFancyText object but not care whether it's mutable or not, but there are others where one will need an immutable derivative of either DrawnText or DrawnFancyText but won't care which. Achieving the former would require EditableDrawnFancyText and ImmutableDrawnFancyText to have a common base; achieving the latter would require ImmutableDraw开发者_StackOverflow中文版nText and ImmutableDrawnFancyText to have a common base. Unfortunately, such a pattern cannot be achieved without multiple inheritance since ImmutableDrawnText has no relationship to EditableDrawnFancyText. Fortunately, interfaces do allow multiple inheritance even though classes do not.

It would seem the best way to achieve the proper inheritance relationship would be to define interfaces:

  1. IDrawnText
  2. IDrawnFancyText : IDrawnText
  3. IEditableDrawnText : IDrawnText
  4. IEditableDrawnFancyText : IEditableDrawnText, IDrawnFancyText
  5. IImmutableDrawnText : IDrawnText
  6. IImmutableDrawnFancyText : IImmutableDrawnText, IIDrawnFancyText

It would seem that having consumers of the class use interfaces rather than classes would achieve all of the proper object relationships. On the other hand, exposing interfaces would mean that consumers would have to trust that nobody implements a so-called "immutable" interface with an object that allows outside mutation.

For non-security-sensitive information, would it be good to use interfaces so as to allow proper inheritance relations, and rely upon implementers not to violate contracts?

Ideally, it would be possible to expose a public interface well enough to allow outside instances to be passed around, without having to allow outside code to define its own implementations. If that were doable, that would seem like the optimal approach. Unfortunately, while one can expose public abstract classes with 'internal'-qualified constructors, I'm unaware of any such ability with interfaces. Still, I'm not sure the possibility of someone implementing "IImmutableDrawnText" with an object that allows outside mutation is necessarily a real problem.

Edit IDrawnText would only expose getters but not setters, but its documentation would explicitly state that objects implementing IDrawnText may or may not be mutable via other means; IImmutableDrawnText would expose the same members as IDrawnText, but the documentation would expressly state that classes which allow mutation are forbidden from implementing the interface. Nothing would prevent mutable classes from implementing IImmutableDrawnText in violation of the contract, but any and all such classes would be broken implementations of that interface.


There's no such thing as an "immutable interface". There's an interface that doesn't declare methods that mutate the object, but no way for it to prohibit mutation. And allowing mutation allows all the thread-safety and "security" issues that go along with it.

The reason immutable classes should be sealed, is that they make that promise that they can't (normally) be modified once created. A subclass can break that promise (and LSP along with it), and prohibiting inheritance is the only way to enforce the promise.

BTW, immutability isn't for security. A language/framework that allows reflection (like, say, C#/.net?) can be used to modify the object virtually at will, ignoring everything the object's done to prevent it. Immutability mainly just makes that hard to do accidentally.


An interface does not define mutability/immutability; rather, it only serves to constrain what the consumer can do. Just because an interface does not expose mutation facilities does not mean you should assume the object is immutable. For example, IEnumerable says nothing about a collection's immutability, but it is not a mutable interface.

Basically: you would need to define that elsewhere - probably in documentation.

If that way of splitting up the functionality helps with your domain, then: go for it! If it doesn't help, don't force it on yourself unnecessarily (never invent a requirement).

Also: if a type is not sealed it can't really claim to be immutable, as the (base) type itself won't even know what is possible. Again, this may or may not be a real problem.


You might look at the WPF Freezable class for inspiration if you want to implement this pattern.

It is described as follows:

Defines an object that has a modifiable state and a read-only (frozen) state. Classes that derive from Freezable provide detailed change notification, can be made immutable, and can clone themselves.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号