Is it possible to have a method that derived classes cannot invoke, but consumers of the class can invoke?

I am implementing a template method template. There are several settings that clients can change in this template method; but I would like the developers of the template method not to change these parameters, because in such cases they could break the invariants of the class of the template method (base). I would like these things to not be able to compile, if possible. (Otherwise, it's time to reorganize the strategy :))

Example:

abstract class TemplateMethod { // Clients of implementers of this template method change these settings before // calling GetItems() public SomeType RootItem { get; set; } public bool Recursive { get; set; } protected abstract bool ItemIsWhitelisted(SomeType item); public IEnumerable<SomeType> GetItems() { var stack = new Stack<T>(); stack.Push(RootItem); while (!stack.empty()) { var current = stack.Pop(); if (Recursive) { foreach (var item in current.Children) stack.Push(item); } if (!ItemIsWhitelisted(current)) yield return current; } } } class Implementer : TemplateMethod { protected override bool ItemIsWhitelisted(SomeType item) { Recursive = false; // Oops; TemplateMethod.GetItems didn't expect this // to change inside ItemIsWhitelisted return item.CanFrobinate(); } } 

One method is strategy refactoring, which creates the following:

 interface IImplementer { bool ItemIswhitelisted(SomeType item); } sealed class NoLongerATemplateMethod { // Clients of implementers of this template method change these settings before // calling GetItems() public SomeType RootItem { get; set; } public bool Recursive { get; set; } public IImplementer impl { get; set; } // would be private set in real code public IEnumerable<SomeType> GetItems() { var stack = new Stack<T>(); stack.Push(RootItem); while (!stack.empty()) { var current = stack.Pop(); if (Recursive) { foreach (var item in current.Children) stack.Push(item); } if (!impl.ItemIsWhitelisted(current)) yield return current; } } } class Implementer : IImplementer { public bool ItemIsWhitelisted(SomeType item) { Recursive = false; // No longer compiles return item.CanFrobinate(); } } 

I am curious if there is a language function that indicates this restriction without applying refactoring to the strategy.

+4
source share
4 answers

You must make the TemplateMethod settings immutable:

 abstract class TemplateMethod { protected TemplateMethod(bool recursive) { Recursive = recursive; } public bool Recursive { get; private set; } protected abstract bool ItemIsWhitelisted(SomeType item); public IEnumerable<SomeType> GetItems() { /* ... */ } } class Implementer : TemplateMethod { protected override bool ItemIsWhitelisted(SomeType item) { Recursive = false; // Oops; Recursive is read-only return item.CanFrobinate(); } } 

UPD

Option number 2. If using ctor is difficult to pass settings, you might consider inserting an immutable parameter object. Something like this (MEF style sample):

 public interface ISettings { bool Recursive { get; } } abstract class TemplateMethod { [Import] public ISettings Settings { get; private set; } protected abstract bool ItemIsWhitelisted(SomeType item); public IEnumerable<SomeType> GetItems() { /* ... */ } } 

Of course, this means that TemplateMethod cannot change the settings either.

Option number 3. Explicit implementation of the interface (if TemplateMethod should be able to change settings):

 public interface ISettingsWriter { bool Recursive { set; } } abstract class TemplateMethod : ISettingsWriter { public bool Recursive { get; private set; } bool ISettingsWriter.Recursive { set { Recursive = value; } } protected abstract bool ItemIsWhitelisted(SomeType item); } class Implementer : TemplateMethod { protected override bool ItemIsWhitelisted(SomeType item) { Recursive = true; // Oops; Recursive is still read-only; return true; } } 

And of course, this means that anyone who wants to change TemplateMethod.Recursive must drop TemplateMethod in ISettingsWriter .

+4
source

I donโ€™t think that you can reasonably imagine such a restriction โ€œfor everyone, but not for my childโ€ (the child is one of all, and itโ€™s impossible to block without blocking everyone, too).

Having no such properties is a different approach altogether (compared to having to do preliminary R / O checks in some way, as suggested by @Dennis)

 public IEnumerable<SomeType> GetItems(bool Recursive)... 

or even

 public IEnumerable<SomeType> GetItems(IterationSettings settings = DefaultSettings) 

Note that this approach (similar to the full strategy template for iteration) also solves the problem when an external "trusted" (not child) caller changes properties in the middle of the iteration (i.e. if there are any callbacks / events, " child "implementation).

+1
source

If your "Framework" is not published with the source code, and the developers use the dll, you can do this:

 public abstract class TemplateMethod { internal void DoSomething() { ... } } public abstract class Consumer { TemplateMethod _Template = ...; protected void DoSomething() { _Template.DoSomething(); } } 
+1
source

You can try using stack tracing to see if the invocation method can change the value or not.

  private bool _recursive; public bool Recursive { get { return _recursive; } set { StackTrace stackTrace = new StackTrace(); if (stackTrace.GetFrame(1).GetMethod().Name == "ItemIsWhitelisted") { throw new Exception("Please don't"); } _recursive = value; } } 

or you can check the DeclaringType method in the stack trace

 stackTrace.GetFrame(1).GetMethod().DeclaringType 
0
source

Source: https://habr.com/ru/post/1438814/


All Articles