I'm making a change to an API that serves data (this is an update to my original question). Some of the searches require data about an author and take a IAuthor
object. The API has an IAuthor
interface and a single concrete class that implements IAuthor
called Author
.
I need to change the behaviour of the Search.GetBooksByAuthor
method to give different semantic when the author is flagged as a novelist. I've heard about the open/closed principle and it would seem that changing the IAuthor
and/or Author
and/or Search
classes would violate this (the Book
class is definitely remaining unchanged, though). How then to make this simple change?
For example, I was originally thinking something like this but my thinking is probably wonky because it involves changing the Search
class:
//Before
class Search
{
public Books[] GetBooks(IAuthor author){
// Call data access GetBooks...
}
}
//After
class Search
{
public Books[] GetBooks(IAuthor author){
// To maintain pre-existing behaviour
// call data access GetBooks method with SQL param @AsNovelist = false...
// (or don't pass anything because the SQL param defaults to false)
}
public Books[] GetBooksAsNovelist(IAuthor author){
// To get new behaviour
// call data access GetBooks method with SQL param @AsNovelist = true
// so that their non-fiction books are omitted from results
}
}
It may seem obvious that something has to change to cater for knowing whether or not your author is a Novelist
, you could do this one of two ways. You don't have to change anything in theory, you do however need a new class.
public class Novelist : Author, IAuthor { }
Then you can pass a novelist into your method and then deterimne your type of author.
class Search
{
public Books[] GetBooks(IAuthor author){
if(author is Novelist)
//Do some stuff or set a flag/bool value
}
}
OR as previously mentioned, implement a boolean member to your Author interface and check that. The above would prevent you changing your class structures however.
This means that your novelist is in fact still an author, it just has it's own type. Your method signatures remain the same, your class structures remain the same you just have a type for a "different type of author", which should in theory be fine. Call as below to test.
GetBooks(new Novelist());
How about using a predicate for filtering?
class Search
{
public Books[] GetBooks(IAuthor author, Func<IAuthor, bool> filter){
// ...
}
}
search.GetBooks(author, a => a.IsNovelist)
In order to extend classes C# .NET introduced extension methods in .NET 3.5 whose main purpose is precisely to extend a class without modifying the existing code:
public static class SearchExtensions
{
public static Books[] GetBooksAsNovelist(this Search search, IAuthor author)
{
// Perform novelist search
}
}
Then you can invoke your Search class normally with:
Search.GetBooksAsNovelist(author);
You can use the Extension feature of C# language.
Please see http://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx
Extensions enable to add functionality to class by keeping the class intact.
In your case you can write as:
public static class SearchExtensions
{
public static Books[] GetBooks(this Search search, IAuthor author)
{
//new logic
}
}
You can access this new method by Search object and Search class also remains intact.
Please let me know if you find this helpful.
You could make your class partial to be able to add functionalyti without extensions, or inheritance, or inversion of control:
// file: Search.cs
partial class Search
{
public Books[] GetBooks(IAuthor author) { ... }
}
// file: Search.Advanced.cs
partial class Search
{
public Books[] GetBooksAsNovelist(IAuthor author) { ... }
}
Results:
http://i.snag.gy/VowNv.jpg
Keep the search class methods as virtual thus anyone can override them creating a new behavior?