Hiding the implementation with accessibility
Object orientation hides the internal implementation of an object and exposes only the operations and attributes that need to be exposed to the outside world. Also, in C#, it is possible to hide classes outside the project. The method of restricting this public range is called accessibility
.
Accessibility | Visibility |
---|---|
public | accessible from outside the project |
internal | accessible only within the same project |
protected | accessible only to derived classes |
private | accessible only inside the class |
Principles of accessibility
Make accessibility as restrictive as possible to stabilize code quality and localize the impact of design changes.
- Make classes internal and reduce public classes as much as possible.
- Methods that are necessary for the internal implementation of the class and should not be accessed from the outside should be private, and methods that should be accessed from inherited classes should be protected.
- Class variables are private and define properties.
- Don't have a setter if the property doesn't need to be changed.
Method hiding example
Let's say you have created a service class like this:
Since CreateModels()
, which is a common process, needs to be called only from within this class, it is hidden with private accessibility.
using NextDesign.Core;
namespace MyExtension.Core
{
/// <summary>
/// create a use case model
/// </summary>
public class UseCaseModelCreationService
{
/// <summary>
/// create an actor
/// </summary>
/// <param name="owner">model that owns the created actor</param>
/// <param name="names">String to set Name of created actor</param>
/// <returns>created actor</returns>
public IEnumerable<IModel> CreateActors(IModel owner, IEnumerable<string> names)
{
var createdModels = CreateModels(owner, "Actors", "Actors", names);
return createdModels;
}
/// <summary>
/// create a use case
/// </summary>
/// <param name="owner">Model that owns the created use case</param>
/// <param name="names">String to set as Name of the created use case</param>
/// <returns>created - use case</returns>
public IEnumerable<IModel> CreateUseCases(IModel owner, IEnumerable<string> names)
{
var createdModels = CreateModels(owner, "UseCases", "UseCase", names);
return createdModels;
}
// Keep the following methods private as they are not called directly from the outside
/// <summary>
/// create the model
/// </summary>
/// <param name="owner">model that owns the created model</param>
/// <param name="fieldName">Field name that owns the created model</param>
/// <param name="className">metaclass name of the model to create</param>
/// <param name="names">String to set as Name of created model</param>
/// <returns>Created model</returns>
private IEnumerable<IModel> CreateModels(IModel owner, string fieldName, string className, IEnumerable<string> names)
{
var createdModels = new List<IModel>();
foreach (var name in names)
{
var model = owner.AddNewModel(fieldName, className);
model.SetField("Name", name);
createdModels.Add(model);
}
return createdModels;
}
}
}
class hiding example
This is an example of sharing logic by grouping it into a class.
Since the ModelCreationService
class will only be used from within the project, we will set its accessibility to internal.
using NextDesign.Core;
namespace MyExtension.Core
{
/// <summary>
/// create a use case model
/// </summary>
public class UseCaseModelCreationService
{
/// <summary>
/// create an actor
/// </summary>
/// <param name="owner">model that owns the created actor</param>
/// <param name="names">String to set Name of created actor</param>
/// <returns>created actor</returns>
public IEnumerable<IModel> CreateActors(IModel owner, IEnumerable<string> names)
{
var modelCreationService = new ModelCreationService();
var createdModels = modelCreationService.CreateModels(owner, "Actors", "Actor", names);
return createdModels;
}
/// <summary>
/// create a use case
/// </summary>
/// <param name="owner">Model that owns the created use case</param>
/// <param name="names">String to set as Name of the created use case</param>
/// <returns>created - use case</returns>
public IEnumerable<IModel> CreateUseCases(IModel owner, IEnumerable<string> names)
{
var modelCreationService = new ModelCreationService();
var createdModels = modelCreationService.CreateModels(owner, "UseCases", "UseCase", names);
return createdModels;
}
}
// The following classes are not exposed outside the project
/// <summary>
/// create the model
/// </summary>
internal class ModelCreationService
{
/// <summary>
/// create the model
/// </summary>
/// <param name="owner">model that owns the created model</param>
/// <param name="fieldName">Field name that owns the created model</param>
/// <param name="className">metaclass name of the model to create</param>
/// <param name="names">String to set as Name of created model</param>
/// <returns>Created model</returns>
public IEnumerable<IModel> CreateModels(IModel owner, string fieldName, string className, IEnumerable<string> names)
{
var createdModels = new List<IModel>();
foreach (var name in names)
{
var model = owner.AddNewModel(fieldName, className);
model.SetField("Name", name);
createdModels.Add(model);
}
return createdModels;
}
}
}
Hiding implementations with interfaces
By thoroughly hiding the implementation by the interface, the class implementation of the domain layer should be internalized in principle. This makes it possible to realize an architecture that is extremely resistant to design changes. See Hiding the implementation with an interface for details.