You are currently viewing a new version of our website. To view the old version click .
Electronics
  • Article
  • Open Access

11 April 2024

Assessing System Quality Changes during Software Evolution: The Impact of Design Patterns Explored via Dependency Analysis

,
and
Department of Computer Science, National Taichung University of Education, Taichung 403514, Taiwan
*
Author to whom correspondence should be addressed.
This article belongs to the Special Issue Advances in Software Engineering and Programming Languages

Abstract

Design patterns provide solutions to recurring problems in software design and development, promoting scalability, readability, and maintainability. While past research focused on the utilization of the design patterns and performance, there is limited insight into their impact on program evolution. Dependency signifies relationships between program elements, reflecting a program’s structure and interaction. High dependencies indicate complexity and potential flaws, hampering system quality and maintenance. This paper presents how design patterns influence software evolution by analyzing dependencies using the Abstract Syntax Tree (AST) to examine dependency patterns during evolution. We employed three widely adopted design patterns from the Gang of Four (GoF) as experimental examples. The results show that design patterns effectively reduce dependencies, lowering system complexity and enhancing quality.

1. Introduction

Design patterns offer significant advantages in software development and have become essential standards for high-quality software design. Software engineers widely adopt them to enhance flexibility, reusability, and maintainability [1,2]. Current research mainly delves into pattern selection and implementation and their impact on systems [3,4,5]. Despite the extensive research on the impact of design patterns on software evolution, there remains a notable research gap in understanding the comprehensive influence of various design patterns on real-world software systems. Existing studies often focus on a limited set of design patterns within specific system versions, neglecting the broader spectrum of design patterns and their effects on long-term software evolution.
Dependency relationships represent the associations, interactions, and behaviors among program elements within a software system [6,7,8]. An evaluation of the impact and utility of design patterns in software systems often considers how design patterns influence dependency relationships, as such effects may have implications for software quality metrics such as complexity, maintainability, and scalability [9,10].
A total of 804 studies published between 2000 and 2018 were examined by Wedyan et al. [11]. The statistical analysis determined that the Factory Pattern, Decorator Pattern, and Observer Pattern are the most commonly used design patterns in three categories: Creational Pattern, Structural Pattern, and Behavioral Pattern. McNatt et al. [12] also mentioned that the Factory Pattern and Observer Pattern are the most frequently referenced design patterns. Similarly, Prechelt et al. [13] conducted research focusing on the Factory Pattern, Observer Pattern, and Decorator Pattern. Therefore, in this study, our attention was directed toward these three widely acknowledged design patterns sourced from the Gang of Four (GoF) [14]: the Factory Pattern, Decorator Pattern, and Observer Pattern. We designed individual experiments for each of the selected design patterns. To present the results generated by these design patterns in the experiments, we chose 39 dependency relationships from the 56 proposed by Huang [7] that are commonly associated with the three selected design patterns. By observing the variations in these dependency relationships in the experiment, the influence of these design patterns on different experimental environments can be determined. Additionally, we created a computational tool with Java Parser [15] to assist in minimizing the interference of the experimental environment during the calculation of dependency relationships. The assistance tool scans example programs employing the specified design patterns and those that do not and identifies code dependency relationships. Furthermore, our investigation included simulations of alterations in dependency relationships during program evolution. This allowed us to illustrate how design patterns can effectively diminish dependencies that arise throughout the evolution of a system, ultimately leading to the optimization of the software system.
In the following sections, we explore background knowledge (Section 2), detail our methodology for identifying dependency relationships (Section 3), present and discuss experimental results (Section 4), and conclude by outlining contributions and future research plans (Section 5).

3. Methodology

This section outlines our research methodology, structured into two parts: identifying dependency relationships and designing experimental procedures. The first part introduces dependency relationships related to design patterns and the computational tools developed to discover these relationships. Once the relationships are defined, the second part outlines the experimental procedure tailored to these relationships.

3.1. Identifying the Dependency Relationships

To assess software quality, this study evaluated dependency relationships in a software system. The upcoming subsection will detail the process of identifying these relationships.

3.1.1. Dependency Relationships Related to Design Patterns

This research specifically focuses on programs within the same package and design pattern. Therefore, certain types of dependency relationships are excluded: ‘contained-in-package’, ‘direct-subpackage-of’, ‘subpackage-of’, ‘innerClass-of’, ‘inner-type’, ‘performs-cast’, ‘instance-of’, and ‘use-known’. Additionally, dependencies related to the Android System, such as ‘indirect-invocation’, ‘intent’, ‘register-receiver’, and ‘filter’, will not be discussed. Furthermore, we categorized the dependency relationships with similar characteristics for the organization, such as ‘modifier’, ‘access-attribute’, and ‘self-delegation’. As a result, out of the 56 dependency relationships proposed by Huang [7], 39 relationships were selected in this research, as illustrated in Figure 1 (Dependency relationships with underlines indicate that they were excluded from this research). These chosen dependencies will be categorized into encapsulation, abstraction, and delegation.
Figure 1. Categorization of dependency relationships.

3.1.2. Dependency Relationship Calculation Tool

In order to calculate dependency relationships in the system, we developed a calculation tool using the API provided by JavaParser [15]. This tool is capable of not only parsing Java source code but also generating the Abstract Syntax Tree (AST). This AST is analyzed to identify whether the program contains the aforementioned 39 types of dependency relationships and calculate their quantity. Next, the following is elucidated through illustrative examples. In terms of the system architecture of this assistance tool, as shown in Figure 2, the AST of this program is generated by first passing the source code to Logic Positivizer. Next, the AST is passed separately to Encapsulation Finder, Abstraction Finder, and Delegation Finder for scanning. Finally, the number of dependency relationships for encapsulation, abstraction, and delegation types can be obtained.
Figure 2. System architecture.
We elucidate the calculation method for each type of dependency relationship through mathematical expressions in Table 1, and the signification of the constituent elements within these mathematical formulations is shown in Table 2. Field-in in Table 2, for example, means all the fields declared in the package. By utilizing the mathematical expressions mentioned above within the tool, various dependencies within the code can be effectively identified, making subsequent experiments more convenient.
Table 1. The mathematical expressions of the dependency relationships.
Table 2. Terms and Explanations table.

3.2. Design Experiment Steps

In order to investigate the relationship between design patterns and software quality, we designed the following experimental procedure. It is divided into four steps, as shown in Figure 3. The first step involves designing a sample program written without the use of design patterns. In the second step, a new sample program is designed by applying design patterns to refactor the initial program from step one. Step three simulates the evolution of both sample programs. In step four, the analysis tool is employed to scan the program to identify the number of dependency relationships in the code.
Figure 3. Experimental steps.
Take the Decorator Pattern as an example. The program for the experiment implements a pizza-ordering system, as shown in Figure 4. First, an initial design without the use of design patterns is created, as shown in Figure 4a. In step two, the initial code is refactored using the Decorator design pattern, as shown in Figure 4b. Step three simulates the evolution of both programs, as shown in Figure 4c,d. Finally, the dependency relationship calculation tool is used to scan and calculate the number of dependency relationships in these programs for comparison.
Figure 4. Decorator program design.
This study performed calculations for three types of dependency relationships: encapsulation, abstraction, and delegation. The total number of dependency relationships for each type is identified, as well as the average number of these contained in each class within the program. The results are presented as line charts in the next section.

4. Experiment and Results

This experiment focused on the aforementioned three types of design patterns: Factory Pattern, Decorator Pattern, and Observer Pattern. The purpose of this experiment was to compare the changes in dependency relationships within a program containing these design patterns with the changes in the program without these design patterns.

4.1. Factory Pattern

For the experiment with the Factory Pattern, this study designed a simple beverage store system as an example, with its program design illustrated in Figure 5. Figure 5a represents the initial design of the system, with a BeverageStore offering both GreenTea and BlackTea. With the continual addition of beverage items, this design requires frequent changes in the BeverageStore class to incorporate code for new items. The demand for frequent changes in program elements can be achieved by refactoring with the Factory Pattern, as shown in Figure 5b. Because of the abstraction of beverages through the use of the class SimpleBeverageFactory and the interface IBeverageProvider, it becomes unnecessary to modify the existing BeverageStores’s code when adding new beverage items. Two continuous versions of the BeverageStore system are represented in Figure 5c,d.
Figure 5. Factory program design.
The following section presents our experimental results for the dependency relationships (encapsulation, abstraction, and delegation) in the Factory Pattern.

4.1.1. Encapsulation Dependency Relationships in Factory Pattern

The total number of encapsulation dependency relationships is shown in Table 3, and the average number of encapsulation dependency relationships within each class in the program is displayed in Table 4. By visualizing the experimental results as line charts, it can be observed that, though using the Factory Pattern leads to more encapsulation dependency relationships in the early stages of development, as the program evolves into later stages, the Factory Pattern continues to effectively reduce the number of these relationships and results in lower system complexity. The line representing the Factory Pattern is color-coded in red, as shown in Figure 6 and Figure 7.
Table 3. Total encapsulation dependencies in Factory Pattern.
Table 4. Avg. encapsulation dependencies per class in Factory Pattern.
Figure 6. Factory Pattern: total encapsulation dependencies.
Figure 7. Factory Pattern: avg. encapsulation dependencies per class.

4.1.2. Abstraction Dependency Relationships in Factory Pattern

The total number of abstraction dependency relationships is shown in Table 5, and the average number of abstraction dependency relationships within each class in the program is displayed in Table 6. Although the use of the Factory Pattern generates additional abstraction dependency relationships, the number of these dependency relationships still remains within a reasonable range, as shown in Figure 8 and Figure 9. Furthermore, based on the easy modification of its details without causing significant impacts on other parts, moderate abstraction can make the program easier to maintain and extend.
Table 5. Total abstraction dependencies in Factory Pattern.
Table 6. Avg. abstraction dependencies per class in Factory Pattern.
Figure 8. Factory Pattern: total abstraction dependencies.
Figure 9. Factory Pattern: avg. abstraction dependencies per class.

4.1.3. Delegation Dependency Relationships in Factory Pattern

The total number of delegation dependency relationships is shown in Table 7, and the average number of abstraction dependency relationships within each class in the program is displayed in Table 8. By visualizing the experimental results as line charts, it can be observed that a significant increase in delegation dependency relationships is caused in the program by not using the Factory Pattern, as shown in Figure 10 and Figure 11. Moreover, excessive delegation dependency relationships may lead to higher program coupling, making modifications and extensions more challenging.
Table 7. Total delegation dependencies in Factory Pattern.
Table 8. Avg. delegation dependencies per class in Factory Pattern.
Figure 10. Factory Pattern: total delegation dependencies.
Figure 11. Factory Pattern: avg. delegation dependencies per class.

4.2. Decorator Pattern

For the experiment involving the Decorator Pattern, this study designed a pizza-ordering system calculating the corresponding price based on the selected topping as an example, with its program design illustrated previously in Figure 4a. There are two toppings available, Cheese and Egg, which results in four different combinations and their corresponding prices: Classic, Cheese, Egg, or Cheese and Egg. However, when the restaurant decides to add a third topping, the number of available combinations to choose from will increase significantly, leading to an overly large and excessively complex system, as shown in Figure 4c. This issue can be improved by using the Decorator Pattern. Treating various toppings as different decorators can prevent the proliferation of numerous classes during the initial program evolution, as shown in Figure 4b,d.
The following section presents our experimental results for the dependency relationships (encapsulation, abstraction, and delegation) in the Decorator Pattern.

4.2.1. Encapsulation Dependency Relationships in Decorator Pattern

The total number of encapsulation dependency relationships is shown in Table 9, and the average number of encapsulation dependency relationships within each class in the program is displayed in Table 10. By visualizing the experimental results as line charts, it can be observed that the number of encapsulation dependency relationships in the program that does not contain the Decorator Pattern is significantly higher than that in the one with the Decorator Pattern, as shown in Figure 12 and Figure 13. As mentioned in Section 3.1.1, an excessive number of encapsulation dependency relationships leads to high program complexity, making modifications and extensions challenging.
Table 9. Total encapsulation dependencies in Decorator Pattern.
Table 10. Avg. encapsulation dependencies per class in Decorator Pattern.
Figure 12. Decorator Pattern: total encapsulation dependencies.
Figure 13. Decorator Pattern: avg. encapsulation dependencies per class.

4.2.2. Abstraction Dependency Relationships in Decorator Pattern

The total number of abstraction dependency relationships is shown in Table 11, and the average number of abstraction dependency relationships within each class in the program is displayed in Table 12. By visualizing the experimental results as line charts, it can be observed that the number of abstraction dependency relationships in the program that does not contain the Decorator Pattern is significantly higher than that in the one with the Decorator Pattern, as shown in Figure 14 and Figure 15. As mentioned in Section 3.1.1, high levels of abstraction dependency relationships make program maintenance and management challenging.
Table 11. Total abstraction dependencies in Decorator Pattern.
Table 12. Avg. abstraction dependencies per class in Decorator Pattern.
Figure 14. Decorator Pattern: total abstraction dependencies.
Figure 15. Decorator Pattern: avg. abstraction dependencies per class.

4.2.3. Delegation Dependency Relationships in Decorator Pattern

The total number of delegation dependency relationships is shown in Table 13, and the average number of abstraction dependency relationships within each class in the program is displayed in Table 14. Although programs using the Decorator Pattern have more delegation dependency relationships, their number still remains within a reasonable range, as shown in Figure 16 and Figure 17. As mentioned in Section 3.1.1, moderate delegation can make the program easier to evolve and modify while also enhancing modularity and readability.
Table 13. Total delegation dependencies in Decorator Pattern.
Table 14. Avg. delegation dependencies per class in Decorator Pattern.
Figure 16. Decorator Pattern: total delegation dependencies.
Figure 17. Decorator Pattern: avg. delegation dependencies per class.

4.3. Observer Pattern

For the experiment involving the Observer Pattern, this study designed a simple subscription notification system as an example, with its program design illustrated in Figure 18. When the Youtuber class updates its status, the Subscriber class will be notified, as shown in Figure 18a. However, the Youtuber class and Subscriber class are highly coupled in this design, and the Youtuber must know which Subscribers it has in order to notify them. Furthermore, as the number of Subscribers increases, the program will also become overly large.
Figure 18. Observer program design.
This issue can be improved by using the Observer Pattern. The Youtuber can notify all Subscribers without needing to know the details of individual Subscribers through the interfaces IYoutuber and ISubscriber. Additionally, when adding new Subscribers, there is no need for repeated modifications to the existing program, as shown in Figure 18b. Finally, we depict the scenarios before and after system evolution following the refactoring in Figure 18c,d.
The following section presents our experimental results for the dependency relationships (encapsulation, abstraction, and delegation) in the Observer Pattern.

4.3.1. Encapsulation Dependency Relationships in Observer Pattern

The total number of encapsulation dependency relationships is shown in Table 15, and the average number of encapsulation dependency relationships within each class in the program is displayed in Table 16. By visualizing the experimental results as line charts, it can be observed that as the program extends into subsequent stages, using the Observer Pattern allows the refactored program to have a lower number of encapsulation dependency relationships compared to the initial program. It exhibits better performance in terms of system complexity, as shown in Figure 19 and Figure 20.
Table 15. Total encapsulation dependencies in Observer Pattern.
Table 16. Avg. encapsulation dependencies per class in Observer Pattern.
Figure 19. Observer Pattern: total encapsulation dependencies.
Figure 20. Observer Pattern: avg. encapsulation dependencies per class.

4.3.2. Abstraction Dependency Relationships in Observer Pattern

The total number of abstraction dependency relationships is shown in Table 17, and the average number of abstraction dependency relationships within each class in the program is displayed in Table 18. By visualizing the experimental results as line charts, it can be observed that appropriately elevating the program’s abstraction level not only reduces the dependence on the program but also makes it easier to extend and maintain, as shown in Figure 21 and Figure 22.
Table 17. Total abstraction dependencies in Observer Pattern.
Table 18. Avg. abstraction dependencies per class in Observer Pattern.
Figure 21. Observer Pattern: total abstraction dependencies.
Figure 22. Observer Pattern: avg. abstraction dependencies per class.

4.3.3. Delegation Dependency Relationships in Observer Pattern

The total number of delegation dependency relationships is shown in Table 19, and the average number of abstraction dependency relationships within each class in the program is displayed in Table 20. Although the use of the Observer Pattern may result in more delegation dependency relationships during the early stages of system development, as its evolution progresses, these dependency relationships are still reduced compared to the beginning, as shown in Figure 23 and Figure 24. This method can not only maintain the program’s structure but also enhance its maintainability and scalability.
Table 19. Total delegation dependencies in Observer Pattern.
Table 20. Avg. delegation dependencies per class in Observer Pattern.
Figure 23. Observer Pattern: total delegation dependencies.
Figure 24. Observer Pattern: avg. delegation dependencies per class.

4.4. Threats to Validity

This research opted to examine only a restricted set of design patterns in the experiment and analyzed the results based on a subset of dependency relationships. Expanding the scope of the experiment would bolster the assertions. Furthermore, it is essential to select high-quality studies from the literature as references for the experimental sample programs and tools to yield more comprehensive results. In summary, there is still ample room for improvement regarding both credibility and comprehensiveness in this study.

5. Conclusions

The purpose of this study was to analyze the impact of design patterns on system quality during software system evolution. We employed dependency relationships as a gauge of program complexity. By designing a tool to compute the number of these dependency relationships, it became possible to analyze the changes in these dependencies throughout the evolution process. The evaluation of the three frequently utilized design patterns proposed by the Gang of Four (GoF) as experimental examples led to the following conclusions.
Firstly, the use of design patterns effectively reduces the dependencies generated during system evolution. By introducing appropriate abstractions and decoupling mechanisms, design patterns help reduce direct inter-module dependencies, thereby reducing system complexity. This reduction in dependency has a positive impact by enhancing system scalability and readability, making the system more amenable to modifications and extensions.
Secondly, design patterns also have a positive impact on system quality. Through the judicious use of design patterns, the system’s structure becomes clearer and easier to understand, thus enhancing code readability. Furthermore, design patterns provide a generic solution that enables developers to more easily comprehend and design system architectures, thereby increasing system maintainability.
In summary, the results of this study indicate that design patterns play a significant role in software system evolution. By reducing dependencies and enhancing system quality, design patterns provide software developers with effective methods and guidelines to make systems more scalable, readable, and maintainable. In future research, it would be valuable to explore additional design patterns, conduct in-depth, real-world case studies, and develop refined methods for measuring dependencies, contributing to a more comprehensive understanding of the broader impact and benefits of diverse design patterns in software evolution.

Author Contributions

Conceptualization, K.-H.H.; Software, H.-C.S.-T.; Writing—review & editing, K.-H.H. and C.-H.T. All authors have read and agreed to the published version of the manuscript.

Funding

This research received no external funding.

Data Availability Statement

Data are contained within the article.

Conflicts of Interest

The authors declare no conflicts of interest.

References

  1. Nanthaamornphong, A.; Wetprasit, R. A controlled experiment: Do Visitor patterns improve design simplicity? In Proceedings of the 2014 8th Malaysian Software Engineering Conference (MySEC), Langkawi, Malaysia, 23–24 September 2014; pp. 90–95. [Google Scholar] [CrossRef]
  2. Pree, W. Design Patterns for Object-Oriented Software Development; ACM Press/Addison-Wesley Publishing Co.: Boston, MA, USA, 1995. [Google Scholar]
  3. Celikkan, U.; Bozoklar, D. A Consolidated Approach for Design Pattern Recommendation. In Proceedings of the 2019 4th International Conference on Computer Science and Engineering (UBMK), Samsun, Turkey, 11–15 September 2019; pp. 1–6. [Google Scholar] [CrossRef]
  4. Khomh, F.; Guéhéneuc, Y.G. Do Design Patterns Impact Software Quality Positively? In Proceedings of the 2008 12th European Conference on Software Maintenance and Reengineering, Athens, Greece, 1–4 April 2008; pp. 274–278. [Google Scholar] [CrossRef]
  5. Ampatzoglou, A.; Chatzigeorgiou, A.; Charalampidou, S.; Avgeriou, P. The Effect of GoF Design Patterns on Stability: A Case Study. IEEE Trans. Softw. Eng. 2015, 41, 781–802. [Google Scholar] [CrossRef]
  6. Bichsel, B.; Raychev, V.; Tsankov, P.; Vechev, M. Statistical Deobfuscation of Android Applications. In Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security, Vienna, Austria, 24–28 October 2016; pp. 343–355. [Google Scholar] [CrossRef]
  7. Huang, S.W. Towards a Solution to IoT Interoperability through Reverse Engineering. Master’s Thesis, National Taiwan University, Taipei, Taiwan, 2017. [Google Scholar]
  8. Huang, H.Y. Atomic Services Generation from Java-Based Open Source Software. Master’s Thesis, National Taiwan University, Taipei, Taiwan, 2019. [Google Scholar]
  9. Iyapparaja, M.; Sureshkumar, S. Coupling and cohesion metrics in Java for adaptive reusability risk reduction. In Proceedings of the IET Chennai 3rd International Conference on Sustainable Energy and Intelligent Systems (SEISCON 2012), Tiruchengode, India, 27–29 December 2012. [Google Scholar] [CrossRef]
  10. Wand, A. Reuse Metrics and Assessment in Component-Based Development. In Proceedings of the 6th IASTED International Conference on Software Engineering and Applications, Cambridge, MA, USA, 4–6 November 2002; pp. 583–588. [Google Scholar]
  11. Wedyan, F.; Abufakher, S. Impact of design patterns on software quality: A systematic literature review. IET Softw. 2020, 14, 1–17. [Google Scholar] [CrossRef]
  12. McNatt, W.B.; Bieman, J.M. Coupling of design patterns: Common practices and their benefits. In Proceedings of the 25th Annual International Computer Software and Applications Conference, COMPSAC 2001, Chicago, IL, USA, 8–12 October 2001; pp. 574–579. [Google Scholar] [CrossRef]
  13. Prechelt, L.; Unger, B.; Tichy, W.F.; Brossler, P.; Votta, L.G. A controlled experiment in maintenance: Comparing design patterns to simpler solutions. IEEE Trans. Softw. Eng. 2001, 27, 1134–1144. [Google Scholar] [CrossRef]
  14. Gamma, E.; Helm, R.; Johnson, R.; Vlissides, J. Design Patterns: Elements of Reusable Object-Oriented Software; Addison-Wesley Longman Publishing Co., Inc.: Boston, MA, USA, 1995. [Google Scholar]
  15. van Bruggen Matozoid, D. javaparser. GitHub Repository. Retrieved from GitHub. Available online: https://github.com/javaparser/javaparser (accessed on 12 July 2023).
  16. Tahvildari, L.; Kontogiannis, K.; Mylopoulos, J. Quality-driven software re-engineering. J. Syst. Softw. 2003, 66, 225–239. [Google Scholar] [CrossRef]
  17. Ampatzoglou, A.; Chatzigeorgiou, A. Evaluation of object-oriented design patterns in game development. Inf. Softw. Technol. 2007, 49, 445–454. [Google Scholar] [CrossRef]
  18. Maruyama, K.; Shima, K.i. Automatic method refactoring using weighted dependence graphs. In Proceedings of the 21st International Conference on Software Engineering, Los Angeles, CA, USA, 16–22 May 1999; pp. 236–245. [Google Scholar] [CrossRef]
  19. Terrence, P. The Definitive ANTLR Reference; The Pragmatic Bookshelf: Raleigh, NC, USA, 2007. [Google Scholar]
  20. Fischer, G.; Lusiardi, J.; Von Gudenberg, J.W. Abstract Syntax Trees—and their Role in Model Driven Software Development. In Proceedings of the International Conference on Software Engineering Advances (ICSEA 2007), Cap Esterel, France, 25–31 August 2007; p. 38. [Google Scholar] [CrossRef]
  21. Fauzi, E.; Hendradjaya, B.; Sunindyo, W.D. Reverse engineering of source code to sequence diagram using abstract syntax tree. In Proceedings of the 2016 International Conference on Data and Software Engineering (ICoDSE), Denpasar, Indonesia, 26–27 October 2016; pp. 1–6. [Google Scholar] [CrossRef]
  22. Tao, G.; Guowei, D.; Hu, Q.; Baojiang, C. Improved Plagiarism Detection Algorithm Based on Abstract Syntax Tree. In Proceedings of the 2013 Fourth International Conference on Emerging Intelligent Data and Web Technologies, Xi’an, China, 9–11 September 2013; pp. 714–719. [Google Scholar] [CrossRef]
Disclaimer/Publisher’s Note: The statements, opinions and data contained in all publications are solely those of the individual author(s) and contributor(s) and not of MDPI and/or the editor(s). MDPI and/or the editor(s) disclaim responsibility for any injury to people or property resulting from any ideas, methods, instructions or products referred to in the content.

Article Metrics

Citations

Article Access Statistics

Multiple requests from the same IP address are counted as one view.