How to set up smartphones and PCs. Informational portal
  • home
  • OS
  • Splitting code. MVC model

Splitting code. MVC model

Bootstrap framework: fast responsive layout

A step-by-step video tutorial on the basics of responsive layout in the Bootstrap framework.

Learn to typeset easily, quickly and efficiently using a powerful and practical tool.

Layout to order and get paid.

Free Course "Site on WordPress"

Want to master a WordPress CMS?

Get tutorials on WordPress website design and layout.

Learn to work with themes and slice the layout.

Free video course on drawing site design, layout and installation on CMS WordPress!

* Hover your mouse to pause scrolling.

Back forward

A Convenient Approach to Web Development: The MVC Model

What is MVC? In short, this is a development approach that allows you to achieve more structured code and the application as a whole.

MVC stands for Model-View-Controller. Let's talk about this in more detail.

Today, there are two most typical ways to create web applications (sites).

The first way, let's call it "Classic", assumes that one file can contain the code of various programming languages ​​and markup.

For example, at the beginning of a file, a query is made to the database to retrieve some information from it. Here we are dealing with the SQL language - a special query language designed to interact with a database.

After that, as a rule, the html-markup of the page begins (where can we go without it?). Moreover, inside the html-markup, in the right places, inserts of PHP-code are made, which manage the site, are its logic. All in all, we have in one file: SQL, (X) HTML and PHP. This is already a team hodgepodge. Don't forget to add some more CSS and some Javascript here for completeness, and we'll end up with such a mess that the devil himself will break his leg in this file.

Of course, at first you will remember what and how it happens in it, why what is needed, and where changes need to be made to add / remove / modify certain functionality. However, I guarantee you that in a few months you will be looking at your code in bewilderment, trying to remember what is connected with what, what changes will be "pulled" after changing a file, etc.

I am not saying that you need to completely abandon this approach, but it is clear that it must be used wisely and very carefully.

Second way is connected with the use of the scheme "Model-View-Controller".

What is the essence of this approach, and how can using it help you in your work?

The main idea of ​​this approach is the need separation of homogeneous elements into different files... To put it very simply: one file - one language. But this is a very crude example. This is extremely rare.

Besides the idea of ​​splitting different languages ​​into different files, the key concept is also dividing files into groups according to the functions they perform in the application.

This is where we begin with you to take a closer look at the MVC model.

This model implies the division of all files involved in the development of the site into three groups:

1. Files of the "model" group
2. Files of the "controller" group
3. Files of the "view" group

It is important to immediately understand here that the name of the MVC scheme is a convention. In your application, of course, there can be many models, controllers and views (i.e. files that fall under these groups according to their functions and internal structure).

So let's take a look at a comparative diagram of the MVC model and the "classic" way of development.


On the left you see exactly what we talked about above. At the top of the page - SQL queries to the database. Then the markup plus PHP inserts.

On the right is the simplest diagram of the MVC model. Within the framework of this scheme operations related to interaction with the database occur in the model: retrieving data, modifying and deleting them, counting the number of records in certain tables, etc.

The controller contains the application logic, i.e. what defines its functionality.

The view is intended to be shown to the end user..

The bidirectional arrows in the diagram indicate that there is a relationship in the Model - Controller and Controller - View pairs. Let's consider this relationship in more detail using the example of the following diagram.


In this diagram, we have added two new elements: the user's browser and the database. Let's take a look at the whole cycle: from the browser accessing a specific url to the moment the page is displayed for the user:

1. The user enters the address and the browser contacts the controller.

2. The controller is referring to the model.

3. The model accesses the database (for example, to obtain the information necessary for displaying)

4. Information from the database goes back to the model.

5. From the model, information is transferred to the controller.

6. The controller transfers this information to the view.

7. The view is displayed in the browser using the controller.

This is the general scheme of operation of this model. As you can see, the browser and the database are somewhat different in this diagram. Really, the browser can only access the controller as the controller is part of the url... The visitor cannot access anything other than the controller. This is important to understand. A person cannot refer to views or models through the address bar. It only interacts with the controller.

In this regard, we can talk about the controller as a kind of "distribution center". See for yourself: the controller handles user requests, the controller accesses the model, and the controller mediates the view to the browser.

The second element that stands apart is the database. And it is right. Within the MVC concept it is assumed that only models should work with the base however, sometimes this principle is violated. In this case, interaction with the base is made from the controller or even the view.

Of course, you shouldn't go too far in violation of the structure and principles of MVC, but sometimes such a departure from the rules can be very useful in terms of improving the readability of the code and understanding how the application works.

Model by the way, is an optional element in MVC schema... It is quite possible to implement everything that is needed without using models in principle. Naturally, in this case, you will interact with the database from the controller and view files. As you already understood, this is not a very good form. As soon as you decide to work within the framework of this concept, it is recommended to use the models and do it for their intended purpose.

We examined the "extreme" ones, but our trinity remained in the center of the diagram, where the "Model - Controller" and "Controller - View" interactions take place.

After we have studied the basics of this model, we can think about what this approach gives us, why it is preferable to the classical one.

The main benefit of using such a scheme in my work has already been mentioned - it is increasing the structuredness of the code and the application as a whole... It's no secret that the MVC model has been adopted by many framework vendors, including my favorite CodeIgniter.

After all, what is a framework? If we ignore the foreign word, then this is just a framework, a certain structure, according to which you are invited to develop a website. This structure is versatile enough to create almost any site with it. At the same time, which is very important, the framework is at the same time very flexible, allowing you to achieve exactly what you need.

In other words, a framework is a flexible framework that limits you in terms of structure, but not in terms of functionality.

Returning to the question of MVC, you can see that many frameworks use exactly this approach: they give a fairly clear structure of the application, in which there is also a division of files into views, models and controllers. All of this together can save you a lot of time if you spend it one day learning how to use the MVC framework and model.

Other advantages of the MVC model include functional code division... You no longer need to dig into the mess of SQL queries, markup and PHP code. If you need to tweak or change something, you will know exactly which file you need to edit.

Below you can see a part of the file belonging to the "views" group:


And here is a piece of code from the model:


This is what the controller might look like:


A very important advantage, as you can see, is separating the view from the code... It is often necessary to change the design or even the structure of the site in one way or another, while maintaining the same information on the page that was displayed before. And here begins the editing of the jumble of code, which becomes more and more difficult over time.

The advantage of the MVC model lies precisely in the possibility of completely exclude editing the site design when changing the logic of the application... You can change any of three elements: Model, View, Controller. In this case, you do not have to make changes to other elements, since they are to a certain extent autonomous.

The issue of autonomy for models and views is especially relevant. Once you've written them, you can usually use them successfully for different projects with minimal or no edits. This saves you a lot of time by eliminating the need to re-write similar code.

Benefits of using the MVC model within the framework are clear, for example, the same CodeIgniter.

It's no secret that every site has a large number of similar, if not completely identical functions. Suffice it to recall the feedback form or page navigation. These are only the most striking, "external" moments. You will find even more similarities in code that is not visible to the average user, in code that runs on the server.

Almost all web developers are faced with the need to use similar PHP functions, make similar queries to the database, etc. The framework vendors did a very important thing here - they tried to group those functions that are most often used in separate files, giving webmasters and web programmers new opportunities.

Now you can perform frequently needed things without really thinking about what their implementation is. You can write a couple of lines of code instead of a couple of dozen lines, saving your time and focusing more on the logic of the application, rather than how to implement it.

And all this happens within the framework of the MVC concept, allowing you to achieve almost any results using the framework. In doing so, you get a high degree of code readability. What else do you need for comfortable and efficient work?

Afterword: Keep in mind that any structure that was created to make it easier to accomplish a particular task was created precisely to make it easier.

Avoid sticking to MVC when you are sure it is detrimental to your understanding of the application structure. You do not have to "bend" under the model, but it under you.

Dmitry Naumenko

P.S. Wondering which PHP framework to master? Pay attention to CakePHP - it implements the MVC pattern discussed above, and right now you can get a quick introduction to the video to get a general idea of ​​the capabilities of this framework:

Did you like the material and want to thank you?
Just share with your friends and colleagues!


The Model-View-Controller (MVC) pattern is extremely useful for building applications with complex graphical user interfaces or behavior. But for simpler cases, it will also work. In this post, we will create a sapper game based on this pattern. Python was chosen as the development language, but there is no special meaning in this. Patterns do not depend on a specific programming language, and you can easily port the resulting implementation to any other platform.

Advertising

Briefly about the MVC pattern

As the name suggests, the MVC pattern includes 3 components: Model, View, and Controller. Each of the components fulfills its role and is interchangeable. This means that the components are connected to each other only by some clear interfaces, behind which any implementation can lie. This approach allows you to substitute and combine various components, providing the necessary logic of work or the appearance of the application. Let's look at the functions that each component performs.

Model

Responsible for the internal logic of the program. Here we can hide the methods of storing data, as well as the rules and algorithms for processing information.

For example, for one application, we can create several models. One will be debug and the other will be working. The first can store its data in memory or in a file, while the second is already using the database. In fact, this is just a Strategy pattern.

Representation

Responsible for displaying Model data. At this level, we only provide an interface for user interaction with the Model. The point of introducing this component is the same as for providing different ways of storing data based on several Models.

For example, in the early stages of development, we can create a simple console view for our application, and only then add a nicely designed GUI. Moreover, it remains possible to keep both types of interfaces.

In addition, it should be borne in mind that the responsibility of the View is only to timely display the state of the Model. The Controller is responsible for processing user actions, which we will now talk about.

Controller

Provides a link between the Model and user actions resulting from interacting with the View. Coordinates the moments of updating the states of the Model and the View. Makes most decisions about the transitions of an application from one state to another.

In fact, for every action a user can do in a View, a handler must be defined in the Controller. This handler will perform appropriate manipulations on the model and, if necessary, inform the View about changes.

Advertising

Minesweeper game specifications

Enough theory. Now let's get down to practice. To demonstrate the MVC pattern, we will write a simple game: Minesweeper. The rules of the game are quite simple:

  1. The playing field is a rectangular area consisting of cells. In some cells, mines are randomly located, but the player does not know about them;
  2. The player can click on any cell of the playing field with the left or right mouse buttons;
  3. Clicking the left mouse button will open the cell. Moreover, if there is a mine in the cell, then the game ends with a loss. If there are mines in adjacent cells, next to an open cell, then a counter with the number of mines around will be displayed on the open cell. If there are no mines around an open cell, then each adjacent cell will be opened according to the same principle. That is, the cells will open until they either rest against the border of the playing field, or they reach the already open cells, or there is no mine next to them;
  4. Clicking the right mouse button allows you to make notes on the cells. Clicking on a closed cell marks it with a flag, which locks its state and prevents accidental opening. Clicking on a box marked with a flag changes its flag to a question mark. In this case, the cell is no longer blocked and can be opened with the left mouse button. Clicking on a cell with a question mark returns it to the unmarked closed state;
  5. Victory is determined by the state of the game, in which all cells on the playing field are open, except for mined ones.

An example of what we get is shown below:

Minesweeper UML Diagrams

Before moving on to writing the code, it is a good idea to think over the architecture of the application in advance. It should be independent of the implementation language, so UML is best suited for our purposes.

Game Cell State Diagram

Any cell on the playing field can be in one of 4 states:

  1. The cage is closed;
  2. The cage is open;
  3. The cell is marked with a flag;
  4. The cell is marked with a question mark.

Here we have defined only the states that are meaningful to the View. Since mines are not displayed during the game, there is no corresponding state in the basic set. Let's define possible transitions from one cell state to another using the UML State Diagram:

Minesweeper class diagram

Since we decided to create our application based on the MVC pattern, we will have three main classes: MinesweeperModel, MinesweeperView and MinesweeperController, as well as a MinesweeperCell helper class for storing the state of the jail. Consider their class diagram:

The organization of the architecture is pretty straightforward. Here we have simply assigned tasks to each class in accordance with the principles of the MVC pattern:

  1. At the very bottom of the hierarchy is the MinesweeperCell class of the game cell. It stores the position of the cell, specified by the row row and column of the playing field; one of the state states that we described in the previous subsection; information about the presence of a mine in the cell (mined) and the counter of mines in the neighboring cells counter. In addition, it has two methods: nextMark (), which cycles through the states associated with the right-click markings, and open (), which handles the left-click event.
  2. Above is the MinesweeperModel Model class. It is the container for the MinesweeperCells. Its first startGame () method prepares the playing field for the game to start. The isWin () method checks the playing field for a winning state and returns true if the player wins, otherwise it returns false. A similar method isGameOver () is used to check the loss. The openCell () and nextCellMark () methods only delegate actions to the corresponding cells on the playing field, and the getCell () method returns the requested game cell;
  3. The MinesweeperView class of View includes the following methods: syncWithModel () - provides redrawing of the View to display the current state of the playing field in the Model; getGameSettings () - returns the game settings specified by the user; createBoard () - creates a playing field based on the data of the Model; showWinMessage () and showGameOverMessage () display win and lose messages, respectively;
  4. Finally, the Controller class is MinesweeperController. It defines only three methods for each possible player action: startNewGame () is responsible for pressing the "New Game" button in the View interface; onLeftClick () and onRightClick () handle clicks on game cells with the left and right mouse buttons, respectively.

Implementation of the Minesweeper game in Python

It's time to start implementing our project. Let's choose Python as the development language. Then we will write the View class based on the tkinter module.

But let's start with the Model.

MinsweeperModel

The implementation of the model in Python is as follows:

MIN_ROW_COUNT = 5 MAX_ROW_COUNT = 30 MIN_COLUMN_COUNT = 5 MAX_COLUMN_COUNT = 30 MIN_MINE_COUNT = 1 MAX_MINE_COUNT = 800 class MinesweeperCell: # Possible states of the game cell: # closed # closed # opened - opened with a # flag # marked - marked with a question , row, column): self.row = row self.column = column self.state = "closed" self.mined = False self.counter = 0 markSequence = ["closed", "flagged", "questioned"] def nextMark (self): if self.state in self.markSequence: stateIndex = self.markSequence.index (self.state) self.state = self.markSequence [(stateIndex + 1)% len (self.markSequence)] def open (self ): if self.state! = "flagged": self.state = "opened" class MinesweeperModel: def __init __ (self): self.startGame () def startGame (self, rowCount = 15, columnCount = 15, mineCount = 15) : if rowCount in range (MIN_ROW_COUNT, MAX_ROW_COUNT + 1): self.rowCount = rowCount if columnCount in range (MIN_COLUMN_COUNT, MAX_COLUMN_COUNT + 1): self.columnCount = columnCount if mineCount< self.rowCount * self.columnCount: if mineCount in range(MIN_MINE_COUNT, MAX_MINE_COUNT + 1): self.mineCount = mineCount else: self.mineCount = self.rowCount * self.columnCount - 1 self.firstStep = True self.gameOver = False self.cellsTable = for row in range(self.rowCount): cellsRow = for column in range(self.columnCount): cellsRow.append(MinesweeperCell(row, column)) self.cellsTable.append(cellsRow) def getCell(self, row, column): if row < 0 or column < 0 or self.rowCount <= row or self.columnCount <= column: return None return self.cellsTable[ row ][ column ] def isWin(self): for row in range(self.rowCount): for column in range(self.columnCount): cell = self.cellsTable[ row ][ column ] if not cell.mined and (cell.state != "opened" and cell.state != "flagged"): return False return True def isGameOver(self): return self.gameOver def openCell(self, row, column): cell = self.getCell(row, column) if not cell: return cell.open() if cell.mined: self.gameOver = True return if self.firstStep: self.firstStep = False self.generateMines() cell.counter = self.countMinesAroundCell(row, column) if cell.counter == 0: neighbours = self.getCellNeighbours(row, column) for n in neighbours: if n.state == "closed": self.openCell(n.row, n.column) def nextCellMark(self, row, column): cell = self.getCell(row, column) if cell: cell.nextMark() def generateMines(self): for i in range(self.mineCount): while True: row = random.randint(0, self.rowCount - 1) column = random.randint(0, self.columnCount - 1) cell = self.getCell(row, column) if not cell.state == "opened" and not cell.mined: cell.mined = True break def countMinesAroundCell(self, row, column): neighbours = self.getCellNeighbours(row, column) return sum(1 for n in neighbours if n.mined) def getCellNeighbours(self, row, column): neighbours = for r in range(row - 1, row + 2): neighbours.append(self.getCell(r, column - 1)) if r != row: neighbours.append(self.getCell(r, column)) neighbours.append(self.getCell(r, column + 1)) return filter(lambda n: n is not None, neighbours)

At the top, we define the range of valid game settings:

MIN_ROW_COUNT = 5 MAX_ROW_COUNT = 30 MIN_COLUMN_COUNT = 5 MAX_COLUMN_COUNT = 30 MIN_MINE_COUNT = 1 MAX_MINE_COUNT = 800

In general, these settings could also be made part of the Model. However, the size of the field and the number of mines are fairly static information and are unlikely to change frequently.

We then defined the MinesweeperCell class for the game cell. It turned out to be quite simple. In the constructor of the class, the fields of the cell are initialized with default values. Next, to simplify the implementation of cyclic state transitions, we use the auxiliary list markSequence. If the cell is in the "opened" state, which is not included in this list, then nothing will happen in the nextMark () method, otherwise the cell enters the next state, and from the last "questioned" state it "jumps" to the initial "closed" state ". In the open () method, we check the state of the cell, and if it is not equal to "flagged", then the cell goes into the "opened" state.

The following is the definition of the MinesweeperModel Model class. The startGame () method arranges the playing field according to the rowCount, columnCount and mineCount parameters passed to it. For each of the parameters, a check is performed to ensure that it falls within the acceptable range of values. If the transmitted value is out of range, then the value of the playing field parameter does not change. It should be noted that an additional check is provided for the number of mines. If the transferred number of mines exceeds the size of the field, then we limit it to the number of cells without one. Although, of course, such a game does not make much sense and will be completed in one step, so you can come up with some of your own rules for such a case.

The game board is stored as a list of cell lists in the cellsTable variable. Moreover, note that in the startGame () method, only the position value is set for the cells, but the mines have not yet been placed. Instead, the variable firstStep is defined with the value True. This is necessary in order to remove the element of randomness from the first move and prevent instant loss. Mines will be placed after the first move in the remaining cells.

The getCell () method simply returns the cell of the playing field by row and column. If the row or column value is invalid, then None is returned.

The isWin () method returns True if all the remaining unopened cells of the playing field are mined, that is, in case of a victory, otherwise it will return False. The isGameOver () method simply returns the value of the gameOver class attribute.

In the openCell () method, the open () call is delegated to the game cell object, which is located on the playing field in the position specified in the method parameters. If the open cell turns out to be mined, then we set the value of gameOver to True and exit the method. If the game is not over yet, then we see if this is the first move by checking the value of firstStep. If the move is indeed the first, then the mines will be placed on the playing field using the generateMines () auxiliary method, which we will talk about a little later. Next, we count the number of neighboring cells mined and set the corresponding value of the counter attribute for the processed cell. If the counter counter is zero, then we request a list of neighboring cells using the getCellNeighbors () method and recursively call the openCell () method for all closed "neighbors", that is, for cells with the "closed" status.

The nextCellMark () method just delegates the call to the nextMark () method for the cell located at the passed position.

The arrangement of mines occurs in the generateMines () method. Here we just randomly select a position on the playing field and check that the cell in this position is not open and not already mined. If both conditions are met, then we set the value of the mined attribute to True, otherwise we continue looking for another free cell. Do not forget that in order to use the random module in Python, you must explicitly import it with the import random command.

The method of counting the number of mines countMinesAroundCell () around a certain cell of the playing field is entirely based on the getCellNeighbors () method. The request for the "neighbors" of the cell in the getCellNeighbors () method is also very simple. I don’t think you’ll have a problem with it.

MinesweeperView

Now let's get down to presentation. The code for the MinesweeperView class in Python is shown below:

Class MinesweeperView (Frame): def __init __ (self, model, controller, parent = None): Frame .__ init __ (self, parent) self.model = model self.controller = controller self.controller.setView (self) self.createBoard ( ) panel = Frame (self) panel.pack (side = BOTTOM, fill = X) Button (panel, text = "New game", command = self.controller.startNewGame) .pack (side = RIGHT) self.mineCount = StringVar (panel) self.mineCount.set (self.model.mineCount) Spinbox (panel, from_ = MIN_MINE_COUNT, to = MAX_MINE_COUNT, textvariable = self.mineCount, width = 5) .pack (side = RIGHT) Label (panel, text = "Number of mines:") .pack (side = RIGHT) self.rowCount = StringVar (panel) self.rowCount.set (self.model.rowCount) Spinbox (panel, from_ = MIN_ROW_COUNT, to = MAX_ROW_COUNT, textvariable = self.rowCount , width = 5) .pack (side = RIGHT) Label (panel, text = "x") .pack (side = RIGHT) self.columnCount = StringVar (panel) self.columnCount.set (self.model.columnCount) Spinbox (panel, from_ = MIN_COLUMN_COUN T, to = MAX_COLUMN_COUNT, textvariable = self.columnCount, width = 5) .pack (side = RIGHT) Label (panel, text = "Field size:") .pack (side = RIGHT) def syncWithModel (self): for row in range (self.model.rowCount): for column in range (self.model.columnCount): cell = self.model.getCell (row, column) if cell: btn = self.buttonsTable [row] [column] if self .model.isGameOver () and cell.mined: btn.config (bg = "black", text = "") if cell.state == "closed": btn.config (text = "") elif cell.state = = "opened": btn.config (relief = SUNKEN, text = "") if cell.counter> 0: btn.config (text = cell.counter) elif cell.mined: btn.config (bg = "red") elif cell.state == "flagged": btn.config (text = "P") elif cell.state == "questioned": btn.config (text = "?") def blockCell (self, row, column, block = True): btn = self.buttonsTable [row] [column] if not btn: return if block: btn.bind (" "," break ") else: btn.unbind (" ") def getGameSettings (self): return self.rowCount.get (), self.columnCount.get (), self.mineCount.get () def createBoard (self): try: self.board.pack_forget () self.board .destroy () self.rowCount.set (self.model.rowCount) self.columnCount.set (self.model.columnCount) self.mineCount.set (self.model.mineCount) except: pass self.board = Frame (self ) self.board.pack () self.buttonsTable = for row in range (self.model.rowCount): line = Frame (self.board) line.pack (side = TOP) self.buttonsRow = for column in range (self .model.columnCount): btn = Button (line, width = 2, height = 1, command = lambda row = row, column = column: self.controller.onLeftClick (row, column), padx = 0, pady = 0) btn.pack (side = LEFT) btn.bind (" ", lambda e, row = row, column = column: self.controller.onRightClick (row, column)) self.buttonsRow.append (btn) self.buttonsTable.append (self.buttonsRow) def showWinMessage (self): showinfo ( "Congratulations!", "You won!") Def showGameOverMessage (self): showinfo ("Game over!", "You lost!")

Our View is based on the Frame class from the tkinter module, so don't forget to run the appropriate import command: from tkinter import *. Model and Controller are passed in the constructor of the class. The createBoard () method is immediately called to compose the playing field from cells. I will say in advance that for this purpose we will use ordinary Buttons. Then a Frame is created, which will act as the bottom panel for specifying game parameters. On this panel, we sequentially place the "New Game" button, the handler of which becomes our Controller with its startNewGame () method, and then three Spinbox counters so that the player can indicate the size of the playing field and the number of mines.

The syncWithModel () method simply loops through each cell in a double loop and changes the appearance of the button that represents it in our GUI accordingly. For the sake of simplicity, I have used text symbols for displaying symbols, but it is not so difficult to change text to graphics from external graphics files.

Also, notice that we are using the SUNKEN button style to represent the open cell. And in case of a loss, open the location of all mines on the playing field, showing the corresponding buttons in black, and highlight the button corresponding to the last opened cell with a mine in red:

The next blockCell () method serves as a helper and allows the controller to set the blocking state for buttons. This is necessary to prevent accidental opening of the game cells marked with a flag, and is achieved by installing an empty handler for clicking the left mouse button.

The getGameSettings () method only returns the values ​​of the counters located in the lower panel with the size of the playing field and the number of mines.

The playing field view is created in the createBoard () method. First of all, there is an attempt to delete the old playing field, if it existed, and we also try to set the values ​​of the counters from the panel in accordance with the current configuration of the Model. Then a new Frame is created, which we will call board, to represent the playing field. We compose the buttonsTable table in the same way as the game cells in the Model using a double loop. Each button's handlers are attached to the Controller's onLeftClick () and onRightClick () methods for left and right mouse clicks, respectively.

The last two methods showWinMessage () and showGameOverMessage () just display dialog boxes with appropriate messages using the showinfo () function. In order to use it, you need to import one more module: from tkinter.messagebox import *.

MinesweeperController

So we got to the implementation of the Controller:

Class MinesweeperController: def __init __ (self, model): self.model = model def setView (self, view): self.view = view def startNewGame (self): gameSettings = self.view.getGameSettings () try: self.model. startGame (* map (int, gameSettings)) except: self.model.startGame (self.model.rowCount, self.model.columnCount, self.model.mineCount) self.view.createBoard () def onLeftClick (self, row, column): self.model.openCell (row, column) self.view.syncWithModel () if self.model.isWin (): self.view.showWinMessage () self.startNewGame () elif self.model.isGameOver (): self.view.showGameOverMessage () self.startNewGame () def onRightClick (self, row, column): self.model.nextCellMark (row, column) self.view.blockCell (row, column, self.model.getCell (row, column) .state == "flagged") self.view.syncWithModel ()

We have added the setView () method to bind the View to the Controller. This is because if we wanted to pass a View to the constructor, then this View would have to already exist before the controller was created. And then a similar solution with an additional method for binding would simply pass from the Controller to the View, in which the setController () method would appear.

The startNewGame () handler method for clicking on the New Game button first requests the game parameters entered in the View. The game parameters are returned as a tuple of three components, which we are trying to convert to int. If everything goes well, then we pass these values ​​to the startGame () method of the Model to build the playing field. If something goes wrong, we will simply re-create the playing field with the old parameters. Finally, we send a request to create a new display of the game board in the View by calling the createBoard () method.

The onLeftClick () handler first instructs the Model to open the game cell at the player's selected position. Then it informs the View that the state of the Model has changed and suggests redrawing everything. Then the Model is checked for a win or loss state. If any of this happened, then first a request is sent to the View to display the corresponding notification, and then the startNewGame () handler is called to start a new game.

Right-clicking is handled in the onRightClick () method. In the first line, the nextCellMark () method of the Model is called to cyclically change the label of the selected game cell. Depending on the new state of the cell, a request is sent to the View to set or unlock the corresponding button. And at the end, the View is updated again to display the current state of the Model.

Combining Model, View and Controller

Now all that remains is to connect all the elements within our implementation of Minesweeper based on the MVC pattern and start the game:

Model = MinesweeperModel () controller = MinesweeperController (model); view = MinesweeperView (model, controller) view.pack () view.mainloop ()

Conclusion

So we looked at the MVC pattern. We briefly walked through the theory. And then, step by step, we created a full-fledged game application, going from setting the problem and designing the architecture to implementation in the Python programming language using the tkinter graphics module.

Pattern Model-View-Controller (MVC), discovered in the late 1970s, is a software architecture design pattern that focuses on decoupling data functions from their presentation. In theory, a well-designed MVC application will allow front-end and back-end developers not to interfere with each other's areas of responsibility during their work, that is, the front-end developer does not need to know anything about the "kitchen" of his back-end colleague and vice versa.

Although originally designed for desktop application development, MVC has been adapted for today's needs and is immensely popular with web developers because separation of concerns has made it possible to create clearer, reusable code. The MVC pattern results in clear, modular systems that allow developers to make changes to existing code very quickly.

In this article, we'll walk you through the basics of MVC, starting by defining a pattern and continuing with a small example. This article will be primarily useful for those who have never encountered this pattern in their life, and also, possibly, for those who wish to brush up on their knowledge of MVC.

Understanding MVC

As already mentioned, the name of the pattern comes from the abbreviation of three words: Model View and Controller... Briefly, the principle of the pattern can be illustrated with one diagram (can be found on Wikipedia):

This diagram clearly shows the unidirectional flow of information in the pattern, and also describes the roles of each component.

Model

The model is used to access and manipulate data. In most cases, a model is what is used to access a data store (such as a database). The model provides an interface for data retrieval, creation, modification, and deletion from storage. In the context of the MVC pattern, the model mediates between the view and the controller.

An extremely important feature of the model is that it technically has no knowledge of what is going on with the data in the controller and the view. The model should never make or expect any requests to / from other components of the pattern.

However, always remember that a model is not just a gateway to a database or other system that does nothing but pass data back and forth. A model is like a gateway to data. The model is in most cases the most complex part of the system, in part because the model itself is the glue for all the other parts.

Representation

The view is where the data received from the model is displayed in the desired form. In traditional web applications developed with the MVC pattern, a view is the part of the system where HTML is generated. The view is also responsible for receiving actions from the user in order to send them to the controller. For example, a view renders a button in the user interface, and upon clicking it, invokes the corresponding controller action.

There are some misconceptions about the purpose of the view, especially among web developers who are just starting to build their applications using MVC. One of the most frequently violated rules is that the view should not communicate with the model in any way, and all the data received by the view should only come from the controller... In practice, developers often ignore this concept, which is at the core of the MVC pattern. Fabio Cevasco's article illustrates this confusing approach to MVC using CakePHP, one of many non-standard MVC frameworks:

It is extremely important to understand that in order to get the correct MVC architecture, there should be no direct interactions between views and models. All the logic of data exchange between them must be implemented in the controllers.

In addition, there is a common misconception that a view is just a template file. As Tom Butler noted, this misconception has a huge scale due to the fact that many developers misunderstand the MVC structure from the very beginning, after which they begin to pour this "knowledge" further, masses of novice developers. In reality, a view is much more than just a template, however, many frameworks built on the basis of the MVC pattern have distorted the concept of a view so much that no one cares how correct their applications are from the point of view of the MVC pattern.

It is also important that the view never works with "pure" data from the controller, that is, the controller never works with the view bypassing the model. In the process of interaction between the controller and the view, the model must always be between them.

Controller

The controller is the final piece of the MVC bunch. The task of the controller is to receive data from the user and manipulate the model. It is the controller, and only he, that is the part of the system that interacts with the user.

In a nutshell, a controller can be described as a collector of information that transfers it to a model for processing and storage. He should not do anything with the data, but only be able to receive it from the user. The controller is associated with one view and one model, thus organizing a unidirectional data flow, controlling it at every stage.

It is very important to remember that the controller starts its work only as a result of user interaction with the view, which calls the corresponding function of the controller. The most common mistake among developers is treating a controller simply as a gateway between the view and the model. As a result, the controller is endowed with the functions that the view should perform (by the way, this is where the idea that the view is just a template file comes from). On top of that, many people dump all the data processing logic altogether, forgetting about what the model is intended for in the MVC pattern.

MVC in PHP

I suggest trying to implement the above in a small application. Let's start by creating the Model, View and Controller classes:

string = "MVC + PHP = Awesome!"; ))controller = $ controller; $ this->

". $ this-> model-> string."

"; } } model = $ model; ))

The main classes are ready. Now, let's link them together and run our application:

output ();

As you can see, the controller has no functionality because the user doesn't interact with the application in any way. All functionality is placed in the view, since our application is solely for displaying data.

Let's expand the application a bit by adding some interactivity to see how the controller works:

string = “MVC + PHP = Awesome, click here!”; ))controller = $ controller; $ this-> model = $ model; ) public function output () (return "

model-> string. "

"; } } model = $ model; ) public function clicked () ($ this-> model-> string = “Updated Data, thanks to MVC and PHP!”))

Finally, let's update the glue code a bit:

($ _GET ["action"]) (); ) echo $ view-> output ();

Outcomes

In this short article, we covered the basic concepts of the MVC design pattern and developed a simple application based on it, although of course we are still very far from using it in real life. In the next article, we will look at the main difficulties that you will face if you are more focused on building an application architecture based on the MVC pattern. Stay tuned!

Developing an application in accordance with the MVC (Model-View-Controller) design pattern is characteristic of Java and in relation to DroidScript seems incomprehensible and unnecessary. Why complicate things? MVC acquired a halo of complexity and "magic" due to the use of beautiful but incomprehensible words (concept, model, business logic, pattern) and complex demonstrations in the context of Java when considering it. It's much simpler: MVC is one of the design patterns that makes additionalsharing code in object oriented environment.

The central element of the MVC model is the controller - a regular DroidScript application from which the code related to the visual markup and appearance of widgets, as well as the data and methods of accessing them, is taken out. By data, we are used to understanding information stored in arrays, files, databases. But in the MVC concept, data is understood in the broad sense of the word - it is everything that is not application code:

  • external data from files and databases - metadata, text, graphics, sounds, music, etc.
  • internal application data - lines with labels on buttons and other controls, text in dialog boxes, descriptions of styles, constants, programmatically generated graphics, etc.

From the user's point of view, his work with the application using MVC has not changed: he also clicks on buttons, selects data and how it is displayed. Changes may concern facilities of this work. And on the development side, the changes are tangible: the interaction between data and their display in the MVC concept occurs through the controller and under its control.

Let's start by looking at a simple example of using MVC in a single file application.

Single File MVC Implementation

Let's take a simple application.

Function OnStart () (var _lay = app.CreateLayout ("linear", "VCenter, FillXY"); var _btnShowVersion = app.CreateButton ("Show version", 0.3, 0.1); _btnShowVersion.SetBackColor ("# 66778976"); _btnShowVersion.SetMargins (0, 0.05, 0, 0); _btnShowVersion.SetOnTouch (function () (_btnShowVersion.SetText ("Application Version 1.0");)); _lay.AddChild (_btnShowVersion); (_AddLay);

At first glance, everything looks good, but suppose you want to change the color scheme of the application and display labels in several languages. This will lead to complications, since all data in the shown example are fixed values ​​(literals). This significantly reduces the flexibility of the code and makes it harder to debug and maintain.

Another drawback is that the data - the labels on the button, the markup - the methods for displaying widgets and the action - the block of code that changes the label on the button when you click on it, are in one block and in one file. That is, to change the label, you need to open this file and gain access to the entire application code. It is as if in order to replace the wheel of a car, you had to disassemble the body of the car to gain access to all the contents. For what? In the process of disassembling the car body, you can accidentally hook something and render it inoperative. It is also possible in the code: I wanted to replace the name of the line in one place, but the replacement occurred in the entire file, which led to the appearance of a scattering of errors. Or I just wanted to change the color of the button, but I accidentally hooked on the nearby code and the whole application stopped working.

One of the tasks of the MVC pattern is precisely to differentiate access: first, the module (or block of code) that is the source of the error is determined, and then only access to it is given. Why give access to the electronics and motor of the car, if you need to replace the wheel?

If development is carried out in one file, then it often happens like this: new functions are placed in place, at the very beginning or end of the code, which eventually leads to their mixing. Let's add here the mixing of the code in the functions themselves, and in a month, even with comments, it will not be easy to figure it out.

Let's implement the example shown above in the context of MVC. To do this, all the code must be divided and grouped in the appropriate blocks. The order of blocks in the code is not important, but it is better to adhere to logic: for the controller to work, both data and elements are needed to display them, so it is placed last. At the time the data is displayed, they must exist. So the model block comes first:

  1. Model
  2. Representation
  3. Controller
// +++ model (function () (var _obj =; // +++ data var _version = "Application version 1.0"; var _titleShowVersion = "Show version"; // --- data
// +++ public methods for accessing data _obj.getVersion = function () (return _version;) _obj.btnGetTitle = function () (return _titleShowVersion;) // --- public methods for accessing data window.model = _obj; // open access to the local object)) (); // --- model // +++ view (function () (var _lay = app.CreateLayout ("linear", "VCenter, FillXY"); var _btnShowVersion = app.CreateButton (window.model.btnGetTitle (), 0.3, 0.1); _btnShowVersion.name = "_btnShowVersion"; _btnShowVersion.SetBackColor ("# 66778976"); _btnShowVersion.SetMargins (0, 0.05, 0, 0); _lay.AddChild (_btnShow)

)) (); // --- view // +++ controller (function (p_object) (var _obj =; // public object search method _obj.findObjectById = function (p_name) (var _objectList = app.GetObjects (); for (var _i in _objectList) (if (_objectList [_i] .name == p_name) (return _objectList [_i];)) return null;) window.control = _obj;)) (); function OnStart () (var _buttonShowVersion = window.control.findObjectById ("_ btnShowVersion"); // +++ action _buttonShowVersion.SetOnTouch (function () (this.SetText (window.model.getVersion ());)); / / --- action) // --- controller

Due to the separation of functions, the application code has grown several times.

Initially, all the variables are made private and only at the end, if necessary, access to them through the global window object is opened, which makes it possible to do without global variables.

The example implements the search for a widget, as in Java, but you can do it easier and make the code more efficient by opening access to the object through the global associative array:

Window.controls =;
window.controls.buttonShowVersion = _btnShowVersion;

The data, their display and reaction to actions are in different blocks, without mixing with each other, which makes it easier for the developer to work with them. The easier it is to work with data and code, the fewer errors there will be, the easier it is to debug, maintain, and scale.

It is not necessary to separate all three from each other. There are several variations of MVC, as well as incomplete implementations of this model. For example, you can decouple data and combine action code with controls using anonymous callback functions.

When working in an object-oriented environment, the separation of code and data is already present initially: data and actions are grouped in classes, objects interact with each other through public methods, etc. Thanks to MVC, there is a more subtle and explicit separation of code and data according to their main functions.

For a deeper understanding of the benefits of using the MVC model, consider splitting your code into separate files.

Three-file implementation of the MVC model

The division of the code into different files is used for more convenient work with it. The huge number of small files that can be seen in MVC projects may question this statement, but seeing files is one thing, but working with them is quite another. At any given time, the developer interacts with one file from a small set of them. To do this, you need to have a good understanding of the structure of the project organization and constantly monitor triple files- model, view and controller, so as not to accidentally edit third-party code. Due to the limitations of the DroidScript editor, this grouping is only possible by filenames in the root directory, for example:

myproject_model.js - model
myproject_view.js - view
myproject_control.js - controller

Below is an example of splitting the code of the previous example into files.

myproject_model.js - model(function () (var _obj =; // +++ data var _version = "Application version 1.0"; // --- data // +++ string resource var _titleShowVersion = "Show version"; // +++ string resource _obj.getVersion = function () (return _version;) _obj.btnGetTitle = function () (return _titleShowVersion;) window.model = _obj;)) (); myproject_view.js - view(function () (var _lay = app.CreateLayout ("linear", "VCenter, FillXY"); var _btnShowVersion = app.CreateButton (window.model.btnGetTitle (), 0.3, 0.1); _btnShowVersion.name = "_btnShowVersion ; _btnShowVersion.SetBackColor ("# 66778976"); _btnShowVersion.SetMargins (0, 0.05, 0, 0); _lay.AddChild (_btnShowVersion); app.AddLayout (_lay);)) (); myproject_control.js - controller app.LoadScript ("myproject_model.js"); app.LoadScript ("myproject_view.js");(function (p_object) (var _obj =; // object search method _obj.findObjectById = function (p_name) (var _objectList = app.GetObjects (); for (var _i in _objectList) (if (_objectList [_i] .name = = p_name) (return _objectList [_i];)) return null;) window.control = _obj;)) (); function OnStart () (var _buttonShowVersion = window.control.findObjectById ("_ btnShowVersion"); // +++ action _buttonShowVersion.SetOnTouch (function () (this.SetText (window.model.getVersion ());)); / / --- action )

This simple division of code into files was not easy. For this, a connection with the model was established in advance through the public property of the global root object - window.model, and communication with the view through the global array _map through the method app.GetObjects.

The advantage of dividing the code into files is that now you can replace the code with a whole block, for example, to quickly launch a project, implement a simple model, and then replace the file with another more functional one, but with the same name and interface. This approach is used, for example, when repairing equipment. If earlier the repair consisted in a slow and painstaking search and replacement of failed radio components, now the replacement of standard blocks is being carried out. The cost of repairing a complex board is significantly higher than its quick replacement.

It follows from what has been said that a well-designed interface makes it possible to significantly simplify the subsequent integration of modules.

In JavaScript, objects are passed by reference. Changing the properties of the widget in the controller will change the properties most widget. Theoretically, it is possible to separate presentation objects from code objects, as is done in Java, where xml structures are used as the first, but there is not much point in this for two reasons - the absence of a visual interface editor in DroidScript and a limited set of available properties of API objects.

Multi-file implementation of the MVC model

Depending on the tasks and the complexity of the project, the detail of the separation of code and data, in the general case, can be different. You can additionally separate user data from resources, you can drill down to resources by type, group actions, etc. But, the DroidScript editor does not allow full-featured MVC work.

5000 rubles denomination

Number (rs 8225497)

Imitation of a genuine banknote of the Bank of Russia of 1997

Paper. Does not fluoresce in UV rays

Printing method. Flat offset printing - all images. Color rendering is distorted. All images have a characteristic brilliance. Formed by colored dots. In places of bends, shedding of the dye is observed.

PUBLIC PROTECTIVE FEATURES. FRONT SIDE

Watermarks (1,2)


Imitated by overprinting with a white substance on the front side. Distorted watermark images are viewed in reflected light

Latent image (kipp effect) (3)


Not reproduced

Protective fibers

Simulated by overprinting. In UV rays, green and red glow is observed.

Color Variable Dye (4)


The image of the coat of arms has an increased brilliance. OVI effect not reproduced

MVC + Element (5)


Not imitated. Moire lines of the MVC effect are observed from any angle of view; appeared while copying from the original.

Microperforation (6)


Simulated. Micro-perforation holes viewed in reflected light

Variable varnish (7)

The emblem of the Bank of Russia is covered with golden paint. The polarizing effect is not reproduced.

Colorless Stamping (8)

Not imitated

Relief

Not imitated

5000 rubles denomination

Number (rs 8225497)

An imitation of a genuine banknote of the Bank of Russia of 1997.

PUBLIC PROTECTIVE FEATURES. BACK SIDE

Microtext (9,10)


Partially reproduced

Security thread (11)


Imitated by a white substance on the front side, the outputs are imitated by foil stamping. The outlets of the security thread are hardly distinguishable.

MACHINE READABLE PROTECTION FEATURES

Luminescent protection

Partially imitated

IR protection

Partially imitated

Magnetic shielding

Not imitated

Note. According to law enforcement agencies of the Russian Federation, the counterfeit banknote was seized in the Perm Territory.

Material prepared IPK "InterCrim-press".

Top related articles