Below, I define an IInstantNotification Interface. TextNotification Class and EmailNotification Class inherit from this interface.

public interface IInstantNotification<T> {
        List<string> Addresses { get; set; }
        string NotificationContent { get; set; }
        T NotificationArguments { get; set; }
        bool SendNotification();
}

public class TextNotification : IInstantNotification<TextArguments>
{
        public List<string> Addresses { get; set; }
        public string NotificationContent { get; set; }
        public TextArguments NotificationArguments { get; set; }
        public bool SendNotification(){
            //send the message and confirm
            return true;
        }

        public TextNotification(
            List<string> p_Addresses,
            string p_NotificationContent,
            MailArguments p_NotificationArguments)
        {
            Addresses = p_Addresses;
            NotificationContent = p_NotificationContent;
            NotificationArguments = p_NotificationArguments;
        }
}


public class EmailNotification : IInstantNotification<MailArguments>
{
    ...
}

I can then instantiate a class, pass the args, and send the message. Pretty straight forward.

TextNotification TextObj = new TextNotification(myAddresses,myNotifContent, myArgs);
bool success = TextObj.SendNotification();

Instead, I always end up doing something like the following: scrapping the interfaces and putting everything in a static class for organization purposes only.

public static class TextNotification
{
        public static bool SendNotification(
            List<string> p_Addresses,
            string p_NotificationContent,
            MailArguments p_NotificationArguments)
        {
           //send the message and confirm
            return true;
        }   
}


public static class EmailNotification
{
    ...
}

It seems like the steps to take the action of sending a notification now have a lot less overhead (and are just as easy to understand).

bool success = TextNotification.SendNotification(myAddresses, myNotifContent, myArgs);

To my ignorant more functional programming oriented mindset, the latter is pretty simple and it seems like the best way to implement things. I have really been struggling wrapping my mind around the reason for doing it the more "OOP" way (like at the top of my post). I realize this might be too trivial of an example, but it is the best one I could come up with.

This is all coming from a middle-tier application code perspective. Oftentimes, my code intercepts an http request, invokes static functions to execute business-layer actions, and those actions call my data layer (which are just a bunch of static functions) that then call stored procedures and things bubble back up until the response is eventually returned to the client.

Representing business-layer actions with fancy OO design patterns seems pointless.

I humbly ask for someone to help me understand where my thinking is flawed. I really want to embrace the world of OOP and its fancy design patterns, as well as being able to fully leverage C# and its potential as an OOP language. I feel like I am doing a disservice to myself writing functional-first C# code...

有帮助吗?

解决方案

"I realize this might be too trivial of an example"

That is the point. Programs start usually simple and small, but become more and more complex over time. Without introducing more structure during this process, one will easily produce a big ball of unmaintainable mud. OOP provides tools for solving this problem - giving programs more structure.

Said that, there is no need to jump from "no structure" to "fullblown OOP" in one step when a system grows - it is not even desirable. There is a middle-ground, like using classes as simple DTOs or for introducing new datatypes, then converting certain operations on those data types into member functions of those classes. There is also no need to introduce classes, objects and interfaces everywhere, when only a certain area of a program goes beyond a certain complexity.

In your example above, you may consider to introduce a non-static class TextNotification when the program becomes larger. A reason for this could be that you notice many functions where you always pass the same three-fold combination of adresses, content and arguments around. If there is currently no need for using interfaces or member functions, why overcomplicate things by introducing them (yet)?

Later, you may consider to introduce member functions into the class which operate on or with the data. SendNotification may be such a candidate (though it is debatable if a notification should know how to "send itself", or if some other component in your system should be responsible for sending notifications).

Representing business-layer actions with fancy OO design patterns seems pointless.

Introducing OO design patterns without needs is pointless for any program or module, not just for business-layer actions. Use patterns when they make your program more evolvable and maintainable, not just because they seem to be popular. And "fancy" code is never a good idea, neither in a procedural program nor in an OO program.

其他提示

Short answer: OOP principles make code more maintainable.

In your example however, there isn't much need to use a generic interface if you only have 2 types of InstantNotification. But what if you start to add more kinds of Notifications? You'll have to test and define them all individually even though their behavior is essentially the same. This goes back to the DRY principle (Don't Repeat Yourself)

That way, your main program can have a collection (array, list or enumerable) of IInstantNotification without needing to know the exact type, so rather than calling:

TextNotification.SendNotification();
EmailNotification.SendNotification();
PushNotification.SendNotification();
FacebooklNotification.SendNotification();
SalesforceNotification.SendNotification();

It will look more like:

public List<IInstantNotification> Notifications = new List<IInstantNotification>()
foreach (var notification in Notifications)
{
    notification.SendNotification();
}

Another thing to consider is separation of concerns and the single responsibility principle. The interface you defined has one purpose (to describe behavior), if this can be done in one module, it should.

By putting your Notifications directly in the main program, it keeps the code tightly coupled, meaning that if you add a new type of notification, you will need to change the main program accordingly but if you use the abstraction of interfaces then your main program doesn't have to worry about the details of each and every Notification.

As for testing, the use of interfaces allows you to "Mock" behavior (Moq is a popular testing framework). Again, this allows you to write less code by setting the state of an InstantNotification rather than instantiating one along with all its dependencies.

As was already mentioned in the previous reply, the fact that you can do something does not necessarily mean that you have to :) In your case, it depends on whether you need to instantiate your classes and actually work with their object containing various data/states, or not. Using static classes/methods is in a certain way a bit against OOP principles since you actually do not use objects = instances of some class. If it is compliant with your business logic to have just a container for some methods you want to call instantly and they do not depend on objects' state, you can use static class.

Representing business-layer actions with fancy OO design patterns seems pointless.

You're not wrong.

It's pretty common to have the "business logic" layer organized using more procedural* programming, eg using a service-oriented design or the Transaction Script pattern, but have the "domain model" be more object-oriented.

So in your example you really have a "notification service". In modern C# practice you would probably use IoC instead of static classes, but the pattern is really the same. eg you would have something like:

public class TextNotification : INotificationService
{
        string twilioKey;

        public TextNotification( IConfiguration config)
        {
           this.twilioKey = config["TwilioKey"];
        }
        public async Task SendNotification(
            List<string> p_Addresses,
            string p_NotificationContent
            )
        {
           //send the message 
            return;
        }   
}

Which has some OO aspects to support service substitutability and testability, but the business functionality remains purely procedural.

*"functional programming" means something different

许可以下: CC-BY-SA归因
scroll top