3
\$\begingroup\$

I'm trying to learn coding by actually coding my first working and hopefully expandable in future app. Yesterday I asked about optimizing my menu and got some answers about my take on it. Obviously and as expected I got picked bout my casing, code structure and naming. I fully understand and accept criticism as my goal is to learn from more experienced coders.

I want to create this "question" as I'd love to get some feedback about my code. I especially would love some feedback about my access modifiers and usage (actually lack of) of static methods. I also think that I'm trying too much to separate my code into single class files.

BOOK.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Library
{
    public class Book
    {
        public string BookName
        {
            get; set;
        }
        public string BookAuthor
        {
            get; set;
        }
        public int ReleaseDate
        {
            get; set;
        }

        public bool available = true;
        public Book(string name, string author, int releaseDate)
        {
            BookName = name;
            BookAuthor = author;
            ReleaseDate = releaseDate;
        }

    }


}

BOOKLIST.CS

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/*

    List to store books

*/
namespace Library
{
    class BookList
    {
        List<Book> books = new List<Book>();       
        public void addBook(string bookName, string bookAuthor, int releaseDate) // adding book
        {
            books.Add(new Book(bookName, bookAuthor, releaseDate));
        }
        public void deleteBook(string name) // deleting book
        {          
            books.RemoveAll(Book => Book.BookName == name);
        }
    }
}

I'm aware that I abandoned few of methods, I decided to concentrate on quality instead of quantity.

PROGRAM.CS - Main method to start app

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/*

    Simple library program that handles small library with adding/deleting/borrowing/returning/prolinging books. All books are stored in list and objects' statuses can be saved and read to/from textfile.
    Right now it is considered to be a console app, but in near future I'll rewrite it for window app.

*/
namespace Library
{
    class Program
    {
        static void Main(string[] args)
        {
            UserInterface LibraryWindow = new UserInterface();
            LibraryWindow.startApp(400,200);
        }
    }
}

USERINTERFACE.CS - I tried to separate user input logic from methods themselves. That was one of main problems most users here pointed out.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Library
{
    interface ICommand
    {
        string Description { get; }
        void Execute(BookList bookList);
    }

    class AddBookCommand : ICommand
    {
        public string Description => "Add a book.";
        public void Execute(BookList books)
        {
            Console.Clear();
            Console.WriteLine("Please enter name of the book.");
            string Name = Console.ReadLine();
            Console.WriteLine("Please enter author of the book.");
            string Author = Console.ReadLine();
            Console.WriteLine("Please enter date of release.");
            string ReleaseDateString = Console.ReadLine();
            int ReleaseDate;
            do
            {
                bool TryToParseDate = int.TryParse(ReleaseDateString, out ReleaseDate);
                if (TryToParseDate == false)
                {
                    Console.WriteLine("Wrong value, please enter correct year.");
                    ReleaseDateString = Console.ReadLine();
                }
                else
                {
                    bool CorrectDate = Enumerable.Range(0, DateTime.Today.Year).Contains(int.Parse(ReleaseDateString));
                    if (CorrectDate)
                    {
                        ReleaseDate = int.Parse(ReleaseDateString);
                        break;
                    }
                    else
                    {
                        Console.WriteLine("Wrong value, please enter correct year.");
                        ReleaseDateString = Console.ReadLine();
                    }
                }
            }
            while (true);
            books.addBook(Name, Author, ReleaseDate);
            Console.WriteLine("Book added.");
        }
    }

    class DeleteBookCommand : ICommand
    {
        public string Description => "Delete book.";
        public void Execute(BookList books)
        {
            Console.Clear();
            Console.WriteLine("Please enter name of the book.");
            string Name = Console.ReadLine();
            books.deleteBook(Name);
            Console.WriteLine("Book deleted.");
        }
    }

    class ExitCommand : ICommand
    {
        public string Description => "Exit.";
        public void Execute(BookList books) { Environment.Exit(0); }
    }

    class UserInterface
    {


        public void startApp(int sizeX, int sizeY)
        {
            this.mainMenu();
        }

        public void mainMenu()
        {
            var commands = new ICommand[]
            {
                new AddBookCommand(),
                new DeleteBookCommand(),
                new ExitCommand()
            };
            var books = new BookList();
            while (true)
            {
                Console.WriteLine("Welcome to Library.");
                Console.WriteLine("What do you want to do?");


                for (int i = 0; i < commands.Length; i++)
                {
                    Console.WriteLine("{0}. {1}", i + 1, commands[i].Description);
                }

                var userChoice = string.Empty;
                var commandIndex = -1;
                do
                {
                    userChoice = Console.ReadLine();
                }
                while (!int.TryParse(userChoice, out commandIndex) || commandIndex > commands.Length);

                commands[commandIndex - 1].Execute(books);
            }
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

Overall I think your code is OK, you have done some good thinking about the data structure, workflow and user interface. I like the command pattern.

I have the following comments:

  1. The properties of the Book class should be read only (or private setable) as the title etc. of a book will and should never change.
  2. I think Remove is a better word than Delete.
  3. I don't like the way the ExitCommand terminates the program, because the application has no way to do clean up.
  4. You do not check for a negative input value in the command selection loop.
  5. You should be more careful and distinct about how you name your variables etc. Stick to one convention for cases etc.

Below I have tried to sketch another version:

    using System;
    using System.Threading;

    namespace CR142751
    {
      public class Book
      {

      }

      public class Library
      {
        public void AddBook(Book book)
        {

        }

        public void Remove(Book book)
        {

        }

        public Book RemoveBook(string title)
        {
          return default(Book);
        }
      }

      public interface ICommand
      {
        string Description { get; }
        void Execute(Library library);
      }

      class AddCommand : ICommand
      {
        public AddCommand()
        {

        }

        public string Description
        {
          get
          {
            return "Add Book";
          }
        }

        public void Execute(Library library)
        {
          Console.WriteLine("Add Command");
        }
      }

      class RemoveCommand : ICommand
      {
        public string Description
        {
          get
          {
            return "Remove Book";
          }
        }

        public void Execute(Library library)
        {
          Console.WriteLine("Remove Command");
        }
      }

      class ExitCommand : ICommand
      {
        public string Description
        {
          get
          {
            return "Exit";
          }
        }

        public void Execute(Library library)
        {
          Console.WriteLine("Exit Command");
        }
      }

      class UserInputHandler
      {
        private ICommand[] m_commands = new ICommand[] { new AddCommand(), new RemoveCommand(), new ExitCommand() };

        public ICommand GetCommand()
        {
          while (true)
          {
            Console.Clear();
            Console.WriteLine("What do you want to do?: ");

            for (int i = 0; i < m_commands.Length; i++)
            {
              Console.WriteLine($"{i + 1}: {m_commands[i].Description}");
            }

            Console.Write("Enter a command No.: ");
            string input = Console.ReadLine();

            int index;
            if (int.TryParse(input, out index) && index > 0 && index <= m_commands.Length)
            {
              return m_commands[index - 1];
            }

          }
        }
      }


  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        Library library = new Library();
        UserInputHandler inputHandler = new UserInputHandler();

        Console.WriteLine("Welcome to the Library");
        Console.Write("Press Enter to Continue...");
        Console.ReadLine();

        ICommand command = null;
        do
        {
          command = inputHandler.GetCommand();
          command.Execute(library);

          Thread.Sleep(1000);

        } while (!(command is ExitCommand));
      }
      catch (Exception ex)
      {
        Console.WriteLine($"Error: {ex.Message}");
      }

      Console.WriteLine("\nPress Enter to Exit...");
      Console.ReadLine();
    }
  }
    }
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ The main loop shouldn't have any knowledge about concrete commands. I'd rather return a bool from Execute indicating whether the application can continue or not. This way we could termite from any command if something bad happened. \$\endgroup\$ – t3chb0t Sep 29 '16 at 6:57
  • \$\begingroup\$ @t3chb0t: I see your point, but about the main-loops knowledge about commands I do not agree. IMO the UserInputHandler should not do anything but handle the users choises and "notify" the application about those. And for the Execute-commands: If something goes wrong in a Command.Execute() I consider it as an exception, and it should be caught as such. The only normal exit should be by selecting the ExitCommand. (Then I'm missing a try..catch-clause - agreed) \$\endgroup\$ – Henrik Hansen Sep 29 '16 at 7:08
  • \$\begingroup\$ @t3chb0t: In my comment above I misunderstood your first argument. I think anyway it is OK to stop the execution loop when detecting an ExitCommand because it is an application-internal (well known). You could argue that I then should make it sealed or internal. \$\endgroup\$ – Henrik Hansen Sep 29 '16 at 7:23
0
\$\begingroup\$

A few things I noticed:

In the AddBookCommand, you are using TryParse and filling the ReleaseDate variable with the parsed date, then parsing the ReleaseDateString in 2 more places. It would make more sense to just use the ReleaseDate variable instead.

In the DeleteBookCommand you're expecting the user to correctly type the exact name of the book. It would probably be more user friendly to offer the user choices based on what the user types if there isn't an exact match, or at least an option to re-type the name.

One thing you might want to consider is to implement indexed searching. You could use some dictionaries to store dictionaries of books based on common words in the name or description, and months or years in the release date. This would facilitate checking for duplicate books being added as well as finding the right book to delete.

Another thing to consider, is embedding your 3 classes into one main class where all the manipulations of the classes can be more strictly controlled.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Another thing to consider, is embedding your 3 classes into one main class where all the manipulations of the classes can be more strictly controlled. - this sounds like a really terrible advice. Can you elaborate on that? Are you going to create a God-class? \$\endgroup\$ – t3chb0t Oct 10 '16 at 6:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.